Perl:使用 XML::Twig 插入 XML::Twig 节点
Perl: Inserting an XML::Twig node with XML::Twig
我正在比较两个 XML 文件。如果我发现其中一个文件中缺少一个节点,我想将其插入到另一个文件中。这是我一直在尝试的方式:
my $out_file = 'fbCI_report.xml';
open my $fh_out, '>>', $out_file or die "Can't open $out_file for writing: $!";
my $currentReport = XML::Twig->new( pretty_print => 'indented' );
$currentReport->parsefile($path_to_currentReport);
print "Loaded current report.\n";
my $newReport = XML::Twig->new( pretty_print => 'indented' );
$newReport->parsefile($path_to_newReport);
print "Loaded new report.\n";
my $currentRoot = $currentReport->root; # get the root
my $currentBuilds = $currentRoot->first_child(); # get the builds node
my $currentXCR = $currentBuilds->first_child(); # get the xcr node
my $newRoot = $newReport->root; # get the root
my $newBuilds = $newRoot->first_child(); # get the builds node
my $newXCR = $newBuilds->first_child(); # get the xcr node
my @currentXCRarray = $currentBuilds->children('xcr');
my @newXCRarray = $newBuilds->children('xcr');
my $numberOfxcr = $newBuilds->children_count();
foreach my $currentXCRmod ( @currentXCRarray ) {
my $currentID = $currentXCRmod->att("id");
foreach my $newXCRmod (@newXCRarray) {
my $newID = $newXCRmod->att("id");
if ( $newID == $currentID ) {
last;
}
elsif ( $count == $numberOfxcr && $newID != $currentID ) {
my $insert = $currentBuilds->insert_new_elt($newXCRmod);
print "XCR does not exist in current report, adding it..\n";
}
$count++;
}
}
print $fh_out $currentReport->sprint();
close $fh_out;
然而,这并没有插入具有相应子节点的节点,但我猜是对节点的引用:<XML::Twig::Elt=HASH(0x326efe0)/>
。有没有办法正确插入节点?我还没有在 CPAN 网站上找到任何东西。
示例数据,current.xml:
<project>
<builds>
<xcr id="13367" buildable="false">
<artifact name="rb"/>
<artifact name="syca"/>
</xcr>
<xcr id="13826" buildable="false">
<artifact name="dcs"/>
</xcr>
<\builds>
<\project>
new.xml:
<project>
<builds>
<xcr id="13367" buildable="false">
<artifact name="rb"/>
<artifact name="syca"/>
</xcr>
<xcr id="13826" buildable="false">
<artifact name="dcs"/>
</xcr>
<xcr id="10867" buildable="true">
<artifact name="smth"/>
<artifact name="top"/>
<artifact name="tree"/>
</xcr>
<\builds>
<\project>
你是对的 - 那是 XML::Twig::Elt
的字符串化文本。
问题是 - insert_new_elt
创建 一个新元素。因此,您所做的实际上是 "printing" 元素 ID (XML::Twig::Elt=HASH(0x326efe0)
) 并创建一个名为该节点的新节点。
但您不想这样做 - 您想要复制现有的。
所以我建议你要做的是:
my $copied_elt = $currentXCRmod -> copy;
$copied_elt -> paste ( last_child => $currentBuilds );
这将转移元素(进入 'last_child' 位置)。
虽然我建议您的循环也许也是您可以改进的地方 - 我建议您查看 twig_handler,以检查解析时文件中存在哪些 ID:
my %seen_id;
sub collect_ids {
my ( $twig, $element ) = @_;
$seen_id { $element->att('id') } ++;
}
然后在解析时调用它:
my $currentReport = XML::Twig->new(twig_handlers => { 'xcr' => \&collect_ids},
pretty_print=>'indented');
$currentReport->parsefile($path_to_currentReport);
这会让您轻松 compare/copy 哪些存在或不存在。
或者(根据您目前的 XML 样本):
#!/usr/bin/env perl
use strict;
use warnings 'all';
use Data::Dumper;
use XML::Twig;
my $current = XML::Twig -> new ( ) -> parsefile ('test1.xml');
my $new = XML::Twig -> new ( ) -> parsefile ( 'test2.xml');
my $cur_builds = $current -> root -> get_xpath('./builds',0);
foreach my $xcr ( $new -> findnodes('//xcr') ) {
my $id = $xcr -> att('id');
if ( not $current -> findnodes("//xcr[\@id=\"$id\"]") ) {
print "$id not in current, copying\n";
my $copy = $xcr -> copy;
$copy -> paste ( last_child => $cur_builds );
}
}
$current -> set_pretty_print('indented_a');
$current -> print;
您可能应该移动节点(我不记得当您尝试插入一个已经是树的一部分的元素时会发生什么)。所以写 $newXCRmo->move( first_child( $currentBuilds))
看看这是否会改善这种情况。
我没有太多时间看你的代码,所以它可能还有其他问题。
你有你的比较循环"inside out"
此外,测试 $count == $numberOfxcr
永远不会成功,因为循环 foreach my $newXCRmod (@newXCRarray)
会在它为真之前终止
这是您的代码的改进版本,它使用 XPath 表达式以及 List::Util
中的 any
使循环更简洁
use strict;
use warnings 'all';
use XML::Twig;
use List::Util 'any';
my ( $path_to_curr_report, $path_to_new_report ) = qw/ current.xml new.xml /;
my $out_file = 'fbCI_report.xml';
my $curr_report = XML::Twig->new->parsefile($path_to_curr_report);
my $new_report = XML::Twig->new->parsefile($path_to_new_report);
my ($curr_builds) = $curr_report->findnodes('/project/builds');
for my $new_xcr_mod ( $new_report->findnodes('/project/builds/xcr') ) {
my $new_id = $new_xcr_mod->att('id');
next if any { $new_id eq $_->att('id') } $curr_report->findnodes('/project/builds/xcr');
print qq{XCR with ID "$new_id" does not exist in current report. Adding it.\n};
$new_xcr_mod->copy->paste( last_child => $curr_builds );
}
{
$curr_report->set_pretty_print('indented');
open my $fh, '>', $out_file or die "Can't open $out_file for writing: $!";
$curr_report->print($fh);
close $fh;
}
输出
XCR with ID "10867" does not exist in current report. Adding it.
<project>
<builds>
<xcr buildable="false" id="13367">
<artifact name="rb"/>
<artifact name="syca"/>
</xcr>
<xcr buildable="false" id="13826">
<artifact name="dcs"/>
</xcr>
<xcr buildable="true" id="10867">
<artifact name="smth"/>
<artifact name="top"/>
<artifact name="tree"/>
</xcr>
</builds>
</project>
我正在比较两个 XML 文件。如果我发现其中一个文件中缺少一个节点,我想将其插入到另一个文件中。这是我一直在尝试的方式:
my $out_file = 'fbCI_report.xml';
open my $fh_out, '>>', $out_file or die "Can't open $out_file for writing: $!";
my $currentReport = XML::Twig->new( pretty_print => 'indented' );
$currentReport->parsefile($path_to_currentReport);
print "Loaded current report.\n";
my $newReport = XML::Twig->new( pretty_print => 'indented' );
$newReport->parsefile($path_to_newReport);
print "Loaded new report.\n";
my $currentRoot = $currentReport->root; # get the root
my $currentBuilds = $currentRoot->first_child(); # get the builds node
my $currentXCR = $currentBuilds->first_child(); # get the xcr node
my $newRoot = $newReport->root; # get the root
my $newBuilds = $newRoot->first_child(); # get the builds node
my $newXCR = $newBuilds->first_child(); # get the xcr node
my @currentXCRarray = $currentBuilds->children('xcr');
my @newXCRarray = $newBuilds->children('xcr');
my $numberOfxcr = $newBuilds->children_count();
foreach my $currentXCRmod ( @currentXCRarray ) {
my $currentID = $currentXCRmod->att("id");
foreach my $newXCRmod (@newXCRarray) {
my $newID = $newXCRmod->att("id");
if ( $newID == $currentID ) {
last;
}
elsif ( $count == $numberOfxcr && $newID != $currentID ) {
my $insert = $currentBuilds->insert_new_elt($newXCRmod);
print "XCR does not exist in current report, adding it..\n";
}
$count++;
}
}
print $fh_out $currentReport->sprint();
close $fh_out;
然而,这并没有插入具有相应子节点的节点,但我猜是对节点的引用:<XML::Twig::Elt=HASH(0x326efe0)/>
。有没有办法正确插入节点?我还没有在 CPAN 网站上找到任何东西。
示例数据,current.xml:
<project>
<builds>
<xcr id="13367" buildable="false">
<artifact name="rb"/>
<artifact name="syca"/>
</xcr>
<xcr id="13826" buildable="false">
<artifact name="dcs"/>
</xcr>
<\builds>
<\project>
new.xml:
<project>
<builds>
<xcr id="13367" buildable="false">
<artifact name="rb"/>
<artifact name="syca"/>
</xcr>
<xcr id="13826" buildable="false">
<artifact name="dcs"/>
</xcr>
<xcr id="10867" buildable="true">
<artifact name="smth"/>
<artifact name="top"/>
<artifact name="tree"/>
</xcr>
<\builds>
<\project>
你是对的 - 那是 XML::Twig::Elt
的字符串化文本。
问题是 - insert_new_elt
创建 一个新元素。因此,您所做的实际上是 "printing" 元素 ID (XML::Twig::Elt=HASH(0x326efe0)
) 并创建一个名为该节点的新节点。
但您不想这样做 - 您想要复制现有的。
所以我建议你要做的是:
my $copied_elt = $currentXCRmod -> copy;
$copied_elt -> paste ( last_child => $currentBuilds );
这将转移元素(进入 'last_child' 位置)。
虽然我建议您的循环也许也是您可以改进的地方 - 我建议您查看 twig_handler,以检查解析时文件中存在哪些 ID:
my %seen_id;
sub collect_ids {
my ( $twig, $element ) = @_;
$seen_id { $element->att('id') } ++;
}
然后在解析时调用它:
my $currentReport = XML::Twig->new(twig_handlers => { 'xcr' => \&collect_ids},
pretty_print=>'indented');
$currentReport->parsefile($path_to_currentReport);
这会让您轻松 compare/copy 哪些存在或不存在。
或者(根据您目前的 XML 样本):
#!/usr/bin/env perl
use strict;
use warnings 'all';
use Data::Dumper;
use XML::Twig;
my $current = XML::Twig -> new ( ) -> parsefile ('test1.xml');
my $new = XML::Twig -> new ( ) -> parsefile ( 'test2.xml');
my $cur_builds = $current -> root -> get_xpath('./builds',0);
foreach my $xcr ( $new -> findnodes('//xcr') ) {
my $id = $xcr -> att('id');
if ( not $current -> findnodes("//xcr[\@id=\"$id\"]") ) {
print "$id not in current, copying\n";
my $copy = $xcr -> copy;
$copy -> paste ( last_child => $cur_builds );
}
}
$current -> set_pretty_print('indented_a');
$current -> print;
您可能应该移动节点(我不记得当您尝试插入一个已经是树的一部分的元素时会发生什么)。所以写 $newXCRmo->move( first_child( $currentBuilds))
看看这是否会改善这种情况。
我没有太多时间看你的代码,所以它可能还有其他问题。
你有你的比较循环"inside out"
此外,测试 $count == $numberOfxcr
永远不会成功,因为循环 foreach my $newXCRmod (@newXCRarray)
会在它为真之前终止
这是您的代码的改进版本,它使用 XPath 表达式以及 List::Util
中的 any
使循环更简洁
use strict;
use warnings 'all';
use XML::Twig;
use List::Util 'any';
my ( $path_to_curr_report, $path_to_new_report ) = qw/ current.xml new.xml /;
my $out_file = 'fbCI_report.xml';
my $curr_report = XML::Twig->new->parsefile($path_to_curr_report);
my $new_report = XML::Twig->new->parsefile($path_to_new_report);
my ($curr_builds) = $curr_report->findnodes('/project/builds');
for my $new_xcr_mod ( $new_report->findnodes('/project/builds/xcr') ) {
my $new_id = $new_xcr_mod->att('id');
next if any { $new_id eq $_->att('id') } $curr_report->findnodes('/project/builds/xcr');
print qq{XCR with ID "$new_id" does not exist in current report. Adding it.\n};
$new_xcr_mod->copy->paste( last_child => $curr_builds );
}
{
$curr_report->set_pretty_print('indented');
open my $fh, '>', $out_file or die "Can't open $out_file for writing: $!";
$curr_report->print($fh);
close $fh;
}
输出
XCR with ID "10867" does not exist in current report. Adding it.
<project>
<builds>
<xcr buildable="false" id="13367">
<artifact name="rb"/>
<artifact name="syca"/>
</xcr>
<xcr buildable="false" id="13826">
<artifact name="dcs"/>
</xcr>
<xcr buildable="true" id="10867">
<artifact name="smth"/>
<artifact name="top"/>
<artifact name="tree"/>
</xcr>
</builds>
</project>