在 Perl 中从 XML 获取 XSI 类型

Get XSI Type from XML in Perl

在根文件夹的不同子文件夹中有一堆 XML 文件。其中一些有以下内容。

XML-1

  <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Channels>
    <Genre xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="News">
        <CableType>XY-1</CableType>
        <Name>C-SPAN</Name>
    </Genre>
    <displayName>C-SPAN Network</displayName>
    <Genre xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Sports">
        <CableType>XY-2</CableType>
        <Name>Fox</Name>
    </Genre>
    <displayName>Fox Sports</displayName>
</Channels>

XML-2

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Channels>
    <Genre xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="News">
        <CableType>XY-1</CableType>
        <Name>ABC</Name>
    </Genre>
    <displayName>ABC News</displayName>
    <Genre xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Movies">
        <CableType>XY-2</CableType>
        <Name>HBO</Name>
    </Genre>
    <displayName>HBO Movies</displayName>
    <Genre xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="News">
        <CableType>XY-3</CableType>
        <Name>CBS</Name>
    </Genre>
    <displayName>CBS News</displayName>
</Channels>

XML-3

  <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Channels>
    <Genre xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="News">
        <CableType>XY-1</CableType>
        <Name>PBS</Name>
    </Genre>
    <displayName>PBS News</displayName>
    <Genre xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Sports">
        <CableType>XY-@</CableType>
        <Name>ESPN</Name>
    </Genre>
    <displayName>ESPN Network</displayName>
</Channels>

目标是遍历所有子文件夹并解析 XML 并查找 xsi:type 值。大多数 XML 预计只会有一个 xsi:type=News 。但是在这种情况下,XML-2 中有 2 个 xsi:type=News

下面是一个 perl 脚本,到目前为止我可以想出它来遍历所有子文件夹并找到 XML 文件并将其添加到数组列表中。现在需要一些帮助来查找包含多个 xsi:type=News.

的 XML 个文件
my $dir = "C:\perl_scripts";
use File::Find;

find(
{
    wanted => \&findfiles,
},
    $dir
);

sub findfiles
{   
}

my @file_list;
find ( sub {
    return unless -f;       #Must be a file
    return unless /\.xml$/;  #Must end with `.xml` suffix
    push @file_list, $File::Find::name;
}, $dir );

foreach my $title (@file_list) {
    say $title;
}

如何获取xsi:type=News > 1 的总数然后打印到控制台?

对于以上3 XMLs,它应该打印XML-2.

更新:

这是最终代码,

use feature qw(say);
use strict;
use warnings;
use XML::LibXML;

my $dir = "C:\perl_scripts";
use File::Find;

find(
{
    wanted => \&findfiles,
},
    $dir
);

sub findfiles
{   
}

my @file_list;
find ( sub {
    return unless -f;       #Must be a file
    return unless /\.xml$/;  #Must end with `.xml` suffix
    push @file_list, $File::Find::name;
}, $dir );

foreach my $title (@file_list){
    my $doc = XML::LibXML->load_xml(location => $title);
    my %xsi_type;
    for my $node ($doc->findnodes('//Genre')) {
         $xsi_type{ $node->getAttribute('xsi:type') }++;
    }
    if ($xsi_type{News} > 1) {
        print 'Found file with more than one xsi:type="News" ==> ';
        say $title;
    }
}

这是一个示例,说明如何使用 XML::LibXML 确定一个文件是否有多个带有 xsi:type="News" 的标签:

use feature qw(say);
use strict;
use warnings;
use XML::LibXML;

my $xml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Channels>
    <Genre xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="News">
        <CableType>XY-1</CableType>
        <Name>ABC</Name>
    </Genre>
    <displayName>ABC News</displayName>
    <Genre xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Movies">
        <CableType>XY-2</CableType>
        <Name>HBO</Name>
    </Genre>
    <displayName>HBO Movies</displayName>
    <Genre xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="News">
        <CableType>XY-3</CableType>
        <Name>CBS</Name>
    </Genre>
    <displayName>CBS News</displayName>
</Channels>';

my $doc = XML::LibXML->load_xml(string => $xml);
my %xsi_type;
for my $node ($doc->findnodes('//Genre')) {
     $xsi_type{ $node->getAttribute('xsi:type') }++;
}
if ($xsi_type{News} > 1) {
    say 'Found file with more than one xsi:type="News"';
}

您可以要求 News 类型的第二个 Genre[1]

此外,File::Find::Rule 比 File::Find 干净得多。

use strict;
use warnings;

use File::Find::Rule          qw( );
use XML::LibXML               qw( );
use XML::LibXML::XPathContext qw( );

my $root_dir_qfn = $ARGV[0] // ".";

my $xpc = XML::LibXML::XPathContext->new;
$xpc->registerNs( xsi => "http://www.w3.org/2001/XMLSchema-instance" );

for my $qfn (
   File::Find::Rule
   ->file
   ->name( "*.xml" )
   ->in( $dir_qfn )
) {
   my $doc = XML::LibXML->load_xml( string => $xml );

   if ( () = $xpc->findnodes( '/Channels/Genre[@xsi:type="News"][2]', $doc ) ) {
      warn( "$qfn: Found multiple News channels\n" );
   }
}

  1. 之前的答案建议遍历所有 Genres 元素,但没有必要这样做。

    这个答案还展示了如何正确使用命名空间。依赖特定前缀是不可接受的,因为任何前缀都可以使用,只要它具有与之关联的正确命名空间。例如,<Genre xmlns:foo="http://www.w3.org/2001/XMLSchema-instance" foo:type="News"> 是合法的,我的程序将正确识别此元素(与之前的答案不同)。