如何比较不同XML文档中的某些值?

时间:2017-03-14 13:56:10

标签: xml perl xml-parsing perl-module

我想在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)

5 个答案:

答案 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_1parse_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:提供深度相等的函数()来比较两个节点(包括文档节点)