xml - sirven - qué significa dtd ?:
¿Cómo comparo ciertos valores de diferentes documentos XML? (5)
Quiero escribir código en Perl que compare dos archivos XML.
Un poco de la historia ... Con la documentación de la API (solicitud de obtención) obtengo datos1 del servicio web1 y datos2 del servicio2. Se presentan en formato XML, pero no es lo mismo.
Debo comparar solo dos elementos en estos archivos (deviceName e ipAddress), si son los mismos en ambos archivos, debería ser un mensaje "WebService1 ya contiene DeviceName" Switch1 "". De lo contrario, haría una solicitud POST y agregaría este dispositivo en WebService1 / WebService2.
¿Me puede dar consejos, qué módulos debería usar y cómo debería comenzar con esta comparación?
Por ejemplo (archivo1)
<?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>
File2
<?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>
Hay algo muy especial: en el archivo 1 mis etiquetas se llaman: deviceName, ipAddress y son elementos .
En el archivo 2 tenemos un atributo (porque se está quedando en el elemento principal ns3: networkdevice y se llama nombre que responde nuestro deviceName del archivo1) y el otro elemento se llama ipaddress (ipAddress en file1)
Al igual que Simbaque , usaría XML::Twig
, aunque lo abordaría de forma ligeramente diferente. Ofrezco esto por el bien de la comparación, en lugar de usar twig_handlers
, lo que llamaría una técnica poderosa y útil, pero particularmente adecuado para el análisis incremental XML más grande: algo que use get_xpath
para buscar referencias basadas en xpath
dentro del XML podría proporcionar una alternativa.
#!/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";
}
Analizamos ambos archivos en un objeto XML::Twig
y luego usamos get_xpath
para buscar la ubicación del nodo. //
significa en cualquier lugar en árbol, y el 0
refiere a qué instancia (por ejemplo, la primera, solamente).
Sin embargo, idealmente podríamos hacer algunas cadenas xpath para compararlas directamente: no podemos hacerlo aquí, porque el atributo ''nombre'' es un atributo del nodo raíz (y una de las limitaciones del motor XML::Twig
xpath es que no se puede seleccionar directamente el contenido del atributo).
Pero con XML::LibXML
, que es más completo, a costa de una curva de aprendizaje algo más empinada. No lo usaría en general, pero en este caso específico puede manejar la expresión xpath
para seleccionar un atributo del nodo raíz.
Entonces eso sería algo así como:
#!/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;
}
En primer lugar, tenga en cuenta que no existe un acuerdo universal sobre lo que significa que dos archivos XML sean "iguales". Por ejemplo, todos están de acuerdo en que los espacios en blanco dentro de las etiquetas de inicio y final deben ignorarse, y que la distinción entre comillas simples y dobles en torno a los atributos es irrelevante, y que los atributos pueden estar en cualquier orden; pero los requisitos varían sobre cómo manejar los comentarios, el espacio en blanco entre las etiquetas de los elementos, los prefijos del espacio de nombres y muchos otros detalles.
Otra área donde los requisitos varían es qué información quiere cuando los documentos se consideran diferentes. Algunos mecanismos solo le darán una respuesta de sí o no, y no lo ayudarán a encontrar las diferencias.
Esto tiene la consecuencia de que puede haber soluciones de propósito general, pero es posible que no siempre cumplan con sus requisitos específicos.
Así que escribir su propio comparador no es una idea ridícula si está preparado para escribir unos cientos de líneas de código.
Pero dos soluciones listas para usar que podría considerar, si puede encontrar ejemplos que se ejecutan en el entorno de Perl, son:
Canonicalizadores de XML: canonicalize ambos documentos y luego compare los resultados en el nivel binario.
XPath 2.0: ofrece la función deep-equal () para comparar dos nodos (incluidos los nodos de documentos)
Esta no es una tarea simple para escribir desde cero. Deberías hacer uso de XML::Compare
Puede usar XML :: Twig para analizar ambas respuestas. Cada uno de ellos necesita un analizador individual.
Para el primero, debe buscar las dos etiquetas <deviceName>
y <ipAddress>
. Un twig_handler
simple para cada uno de ellos que accede a la propiedad de text
del elemento coincidente es suficiente.
Esos manejadores pueden ser complejos, pero en nuestro caso es suficiente una referencia de código que trate de un solo valor. Sabemos que solo hay una ocurrencia de cada valor, por lo que podemos asignarlos directamente a sus respectivas variables léxicas.
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>
Para el segundo, necesita usar att()
para obtener el atributo de nombre de uno de los elementos, pero eso también es sencillo.
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>
Ahora que tienes ambos, puedes combinar eso. Sugiero crear una función para cada uno de ellos, pasar el XML de respuesta y hacer que devuelvan el $device_name
y $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;
}
Por supuesto, mis nombres parse_response_1
y parse_response_2
no son la mejor opción. No use los números, use los nombres de los servicios que devolvieron las respuestas.
Con esas dos funciones, ahora tenemos los medios para recuperar exactamente la información que queremos. Todo lo que queda es controlarlos.
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;
}
De nuevo, los nombres de las variables podrían ser mejores. Ahora solo necesita llamarlo con sus dos respuestas XML y devolverá un valor de verdad, o no.
use XML::Simple;
use Data::Dumper;
my $file1_ref = XMLin("./file1");
my $file2_ref = XMLin("./file2");
if($file2_ref->{NetworkDeviceIPList}->{NetworkDeviceIP}->{ipaddress} eq $file1_ref->{entity}->{devicesDTO}->{ipAddress} && $file2_ref->{name} eq $file1_ref->{entity}->{devicesDTO}->{deviceName}) {
print "WebService1 already contains DeviceName /"".$file2_ref->{name}."/"/n";
} else {
# POST request and add this device in WebService1/WebService2
# Code here ....
}
Puede convertir las llamadas en métodos y le sugiero encarecidamente que agregue y evalúe la conversión y compruebe si hay errores, en caso de que el XML devuelto tenga errores