我想在Perl中编写比较两个XML文件的代码。
历史上的一点点...... 使用API文档(获取请求),我从Web Service1获取data1,从Service2获取data2。它们以XML格式呈现,但不一样。
我应该在这些文件(deviceName和ipAddress)中比较两个元素,如果它们在两个文件中都相同,则应该是消息“WebService1已经包含DeviceName”Switch1“”。如果没有 - 我会发出POST请求并在WebService1 / WebService2中添加此设备。
您能否给我建议,我应该使用哪些模块以及如何从这个比较开始?
例如(file1)
<?xml version="1.0" ?>
<queryResponse last="34" first="0" count="35" type="Devices" responseType="listEntityInstances" requestUrl="https://hostname/webacs/api/v1/data/Devices?.full=true" rootUrl="https://hostname/webacs/api/v1/data">
<entity dtoType="devicesDTO" type="Devices" url="https://hostname/webacs/api/v1/data/Devices/201">
<devicesDTO displayName="201201" id="201">
<clearedAlarms>0</clearedAlarms>
<collectionDetail></collectionDetail>
<collectionTime></collectionTime>
<creationTime></creationTime>
<criticalAlarms>0</criticalAlarms>
<deviceId>205571</deviceId>
<deviceName>NEW-SW5</deviceName>
<deviceType>Cisco Switch</deviceType>
<informationAlarms>0</informationAlarms>
<ipAddress>10.66.12.128</ipAddress>
<location></location>
<majorAlarms>0</majorAlarms>
<managementStatus></managementStatus>
<manufacturerPartNrs>
<manufacturerPartNr></manufacturerPartNr>
</manufacturerPartNrs>
<minorAlarms>0</minorAlarms>
<productFamily></productFamily>
<reachability>Reachable</reachability>
<softwareType>IOS</softwareType>
<softwareVersion>12.1(22)</softwareVersion>
<warningAlarms>0</warningAlarms>
</devicesDTO>
</entity>
</queryResponse>
文件2
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<ns3:networkdevice name="NEW-SW5" id="9a6ef750-2620-11e4-81be-b83861d71f95" xmlns:ns2="ers.ise.cisco.com" xmlns:ns3="network.ers.ise.cisco.com">
<link type="application/xml" href="https://hostname:9060/ers/config/networkdevice/123456" rel="self"/>
<authenticationSettings>
<enableKeyWrap>false</enableKeyWrap>
<keyInputFormat>ASCII</keyInputFormat>
<networkProtocol>RADIUS</networkProtocol>
<radiusSharedSecret>******</radiusSharedSecret>
</authenticationSettings>
<NetworkDeviceIPList>
<NetworkDeviceIP>
<ipaddress>10.66.12.128</ipaddress>
<mask>21</mask>
</NetworkDeviceIP>
</NetworkDeviceIPList>
<NetworkDeviceGroupList>
<NetworkDeviceGroup>Location#All Locations</NetworkDeviceGroup>
<NetworkDeviceGroup>Device Type#All Device Types</NetworkDeviceGroup>
</NetworkDeviceGroupList>
</ns3:networkdevice>
有特殊情况:在file1我的标签名为: deviceName,ipAddress ,它们是元素。
在file2中,我们有一个属性(因为它保留在主元素 ns3:networkdevice 中,它被称为 name 从file1响应我们的deviceName),另一个元素被称为< strong> ipaddress (file1中的ipAddress)
答案 0 :(得分:2)
您可以使用XML::Twig来解析这两个回复。他们每个人都需要一个单独的解析器。
对于第一个,您需要使用两个标记<deviceName>
和<ipAddress>
。对于每个访问匹配元素的twig_handler
属性的简单text
就足够了。
这些处理程序可能很复杂,但在我们的例子中,处理单个值的代码引用就足够了。我们知道每个值只出现一次,因此我们可以直接将它们分配给各自的词汇变量。
use strict;
use warnings;
use XML::Twig;
my ($device_name, $ip_address);
XML::Twig->new(
twig_handlers => {
deviceName => sub { $device_name = $_->text },
ipAddress => sub { $ip_address = $_->text },
}
)->parse(\*DATA);
say $device_name;
say $ip_address;
__DATA__
<?xml version="1.0" ?>
<queryResponse last="34" first="0" count="35" type="Devices" responseType="listEntityInstances" requestUrl="https://hostname/webacs/api/v1/data/Devices?.full=true" rootUrl="https://hostname/webacs/api/v1/data">
<entity dtoType="devicesDTO" type="Devices" url="https://hostname/webacs/api/v1/data/Devices/201">
<devicesDTO displayName="201201" id="201">
<clearedAlarms>0</clearedAlarms>
<collectionDetail></collectionDetail>
<collectionTime></collectionTime>
<creationTime></creationTime>
<criticalAlarms>0</criticalAlarms>
<deviceId>205571</deviceId>
<deviceName>NEW-SW5</deviceName>
<deviceType>Cisco Switch</deviceType>
<informationAlarms>0</informationAlarms>
<ipAddress>10.66.12.128</ipAddress>
<location></location>
<majorAlarms>0</majorAlarms>
<managementStatus></managementStatus>
<manufacturerPartNrs>
<manufacturerPartNr></manufacturerPartNr>
</manufacturerPartNrs>
<minorAlarms>0</minorAlarms>
<productFamily></productFamily>
<reachability>Reachable</reachability>
<softwareType>IOS</softwareType>
<softwareVersion>12.1(22)</softwareVersion>
<warningAlarms>0</warningAlarms>
</devicesDTO>
</entity>
</queryResponse>
对于第二个,您需要使用att()
来获取其中一个元素的 name 属性,但这也是直截了当的。
use strict;
use warnings;
use XML::Twig;
my ($device_name, $ip_address);
XML::Twig->new(
twig_handlers => {
'ns3:networkdevice' => sub { $device_name = $_->att('name') },
ipaddress => sub { $ip_address = $_->text },
}
)->parse(\*DATA);
say $device_name;
say $ip_address;
__DATA__
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<ns3:networkdevice name="NEW-SW5" id="9a6ef750-2620-11e4-81be-b83861d71f95" xmlns:ns2="ers.ise.cisco.com" xmlns:ns3="network.ers.ise.cisco.com">
<link type="application/xml" href="https://hostname:9060/ers/config/networkdevice/123456" rel="self"/>
<authenticationSettings>
<enableKeyWrap>false</enableKeyWrap>
<keyInputFormat>ASCII</keyInputFormat>
<networkProtocol>RADIUS</networkProtocol>
<radiusSharedSecret>******</radiusSharedSecret>
</authenticationSettings>
<NetworkDeviceIPList>
<NetworkDeviceIP>
<ipaddress>10.66.12.128</ipaddress>
<mask>21</mask>
</NetworkDeviceIP>
</NetworkDeviceIPList>
<NetworkDeviceGroupList>
<NetworkDeviceGroup>Location#All Locations</NetworkDeviceGroup>
<NetworkDeviceGroup>Device Type#All Device Types</NetworkDeviceGroup>
</NetworkDeviceGroupList>
</ns3:networkdevice>
现在你拥有这两个,你可以将它们结合起来。我建议为每个函数创建一个函数,传入响应XML并让它们返回$device_name
和$ip_address
。
use strict;
use warnings;
use XML::Twig;
sub parse_response_1 {
my $xml = shift;
my ( $device_name, $ip_address );
XML::Twig->new(
twig_handlers => {
deviceName => sub { $device_name = $_->text },
ipAddress => sub { $ip_address = $_->text },
}
)->parse($xml);
return $device_name, $ip_address;
}
sub parse_response_2 {
my $xml = shift;
my ( $device_name, $ip_address );
XML::Twig->new(
twig_handlers => {
'ns3:networkdevice' => sub { $device_name = $_->att('name') },
ipaddress => sub { $ip_address = $_->text },
}
)->parse($xml);
return $device_name, $ip_address;
}
当然,我的姓名parse_response_1
和parse_response_2
不是最佳选择。不要使用这些数字,而是使用返回响应的服务的名称。
通过这两个功能,我们现在可以准确地检索我们想要的信息。剩下的就是检查它们。
sub check {
my ( $response_1, $response_2 ) = @_;
my ( $device_name_1, $ip_address_1 ) = parse_response_1($response_1);
my ( $device_name_2, $ip_address_2 ) = parse_response_2($response_2);
return $device_name_1 eq $device_name_2 && $ip_address_1 eq $ip_address_2;
}
同样,变量的名称可能会更好。现在你只需要用你的两个响应XML来调用它,它将返回一个真正的值。或者不是。
答案 1 :(得分:2)
很像simbaque我使用XML::Twig
,虽然我的解决方法略有不同 - 我为了比较而提供这个 - 而不是使用twig_handlers
- 我称之为强大且有用的技术,但特别适用于增量解析更大的XML - 使用get_xpath
在XML中查找基于xpath
的引用的东西提供替代方案。
#!/usr/bin/env perl
use strict;
use warnings;
use XML::Twig;
my $xml1 = XML::Twig->new->parsefile('test1a.xml');
my $xml2 = XML::Twig->new->parsefile('test1b.xml');
if ( $xml1->get_xpath( '//deviceName', 0 )->text
eq $xml2->root->att('name') )
{
print "Name matches\n";
}
if ( $xml1->get_xpath( '//ipAddress', 0 )->text
eq $xml2->get_xpath( '//ipaddress', 0 )->text )
{
print "IP matches\n";
}
我们将这两个文件解析为XML::Twig
对象,然后使用get_xpath
查找节点位置。 //
表示树中的任何位置,0
表示哪个实例(例如,第一个,仅)。
理想情况下,我们可能会做一些xpath字符串直接比较 - 我们不能在这里,因为&#39; name&#39; attribute是根节点的属性(XML::Twig
xpath引擎的一个限制是您无法直接选择属性内容)。
但XML::LibXML
- 功能更全面,代价是学习曲线稍微陡峭。我不会使用一般,但在这种特定情况下,它可以处理xpath
表达式来选择根节点的属性。
这就像是:
#!/usr/bin/env perl
use strict;
use warnings;
use XML::LibXML;
my %compare = (
'//deviceName' => '//@name',
'//ipAddress' => '//ipaddress'
);
my $search1 = XML::LibXML::XPathContext->new(
XML::LibXML->load_xml( location => 'test1a.xml' ) );
my $search2 = XML::LibXML::XPathContext->new(
XML::LibXML->load_xml( location => 'test1b.xml' ) );
foreach my $key ( keys %compare ) {
my $first = $search1->find($key);
my $second = $search2->find( $compare{$key} );
print "$key = $first\n";
print "$compare{$key} = $second\n";
print "Matches found\n" if $first eq $second;
}
答案 2 :(得分:1)
从头开始编写这不是一项简单的任务。您应该使用XML::Compare
答案 3 :(得分:1)
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
您可以将调用转换为方法,我强烈建议您在转换时添加和评估并检查错误,以防返回的XML出现错误
答案 4 :(得分:0)
首先请注意,对于两个XML文件的含义没有普遍的一致意见#34;相同的&#34;。例如,每个人都同意应该忽略开始和结束标记中的空格,并且属性周围的单引号和双引号之间的区别是无关紧要的,并且属性可以是任何顺序;但要求如何处理注释,元素标签之间的空格,名称空间前缀和许多其他细节。
需求变化的另一个领域是当文档被视为不同时您想要的信息。有些机制只会给你一个肯定或否定的答案,并且不会帮助你找到差异。
这导致可能存在通用解决方案,但它们可能并不总能满足您的特定要求。
如果您准备编写几百行代码,那么编写自己的比较器并不是一个荒谬的想法。
但是,如果您能找到在Perl环境中运行的示例,您可以考虑的两个现成解决方案是:
XML规范化程序:规范化两个文档,然后在二进制级别比较结果。
XPath 2.0:提供深度相等的函数()来比较两个节点(包括文档节点)