【问题标题】:Get XSI Type from XML in Perl在 Perl 中从 XML 获取 XSI 类型
【发布时间】:2022-01-25 16:21:53
【问题描述】:

在根文件夹的不同子文件夹中有大量 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 个 XML,它应该打印 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;
    }
}

【问题讨论】:

    标签: perl


    【解决方案1】:

    这是一个示例,说明如何使用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"';
    }
    

    【讨论】:

      【解决方案2】:

      您可以使用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 元素,但没有必要这样做。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多