在 Perl 中将 XML 之类的格式转换为 CSV

Convert XML like format to CSV in Perl

所以,我有一个如下所示的文件:

random stuff in the beginning...

 <component>
   <name>bob</name>
   <age>7</age>
   <country>Great_Britain</country>
 </component>

 <component>
   <name>bob_secondbob</name>
   <age>7</age>
   <country>Great_Britain</country>
 </component>

 <component>
   <name>bam</name>
   <age>7</age>
   <country>Great_Britain</country>
 </component>

等...

而且,我想将其作为 CSV 文件,如下所示:

name,age,country
bob,7,Great_Britain
bob_secondbob,7,Great_Britain
bam,7,Great_Britain

所以我想知道我该怎么做?

所以我当前的代码有 bobbam 这样的词,所以我一直在像这样 greping 并使用 sed 来获取值:

grep -A4 "<component>" $file | grep -A4 "<name>$bob.*</name>" | grep "<name>" 
grep -A4 "<component>" $file | grep -A4 "<name>$bob.*</name>" | grep "<age>"
grep -A4 "<component>" $file | grep -A4 "<name>$bob.*</name>" | grep "<country>"

etc...

其中变量$bob"bob";

但问题是 bob.* 有 2 个实例,我不知道如何将它们分开,所以我可以打印出来...

我查看了 XML 模块,但是这个文件不完整 XML 所以我不能使用它...

bam 这样的 grep 很容易打印出来,但是如果有多个 bob.* 的实例,我需要它们,我不知道如何正确打印出来,因为grep 会 return 多个结果。

关于我如何解决这个问题有什么建议吗?

明确指出“文件不完整 XML”并且 XML 库无法使用。无赖:(

然后使用正则表达式对其进行解析。请记住,必须始终关注输入文件以查看其格式是否发生了变化;即使是最小的更改也很容易使正则表达式失效,充其量会导致程序崩溃,或者更糟糕的是,会导致一个安静的错误。

显示的格式很容易解析。这是一个基本的做法,解析 XML-like component 部分的任何标签及其值,然后按需要的顺序打印给定的一组实际标签。

use warnings;
use strict;
use feature 'say';

my $section_name = 'component';   # XML-like section to parse
my @tags = qw(name age country);  # given tags and their order

my (%record, $in_XML);

while (<>) {
    if    (/^\s*<$section_name>\s*$/)   { $in_XML = 1 }
    elsif (/^\s*<\/$section_name>\s*$/) { $in_XML = 0 } 
    
    if ( $in_XML and m{<([^<]+)> ([^<]+) </\g{1}>}x ) { 
        push @{$record{}}, ; 
    }   
}

# Print out CSV-style output, with given tags
say join ',', @tags;
for my $i (0..$#{$record{$tags[0]}}) { 
    say join ',', map { $record{$_}->[$i] } @tags;
}

对标签做了一些假设。一些重要的:每个 tag-pair 在一行上;所有标签名称都是唯一的。如果这些不支持代码需要调整,可以做什么但需要一些工作。

除了匹配 XML-like 打开和关闭 tag-pair、<tagname>...</tagname>,我还添加了一个标志,用于处理 是 [=42] =] 在 component 部分中。在 if 条件内测试标志允许在 XML 之外进行其他处理,否则我们可以在 if 条件之前有 next if not $in_XML;。如果没有机会在文本中的其他地方出现意外 XML-like tag-pair,则整个业务可能是不必要的。

请注意,不必指定和使用 @tags,但可以打印文件中找到的标签,即 my @tags = keys %record,如果可以接受并且顺序无关紧要。

请添加这些标签及其值是否确实符合预期的测试。真实的输入文件往往偶尔会有缺失或意外的部分。


最好补救“不完全XML”(让它XML) 并尽可能使用库。

输入数据是顺序读取的,一旦遇到<component>表示后面有数据

现在我们开始解析数据,直到我们将 </component> 读入哈希,然后将结果推入数组。

处理完整个文件后,将散列的键输出为 header,并通过 ,.

连接每个元素的数据散列值
use strict;
use warnings;
use feature 'say';

my @data;
my @fields = qw/name age country/;

while( <DATA> ) {
    if( /<component>/ ) {
        my $component;
        while( <DATA> ) {
            last if /<\/component>/;
            $component->{} =  if /^\s+<(.+?)>(.*?)</;
        }
        push @data, $component;
    }
}

say join ',', @fields;
say join ',', $_->@{@fields} for @data;

__DATA__


 <component>
   <name>bob</name>
   <age>7</age>
   <country>Great_Britain</country>
 </component>

 <component>
   <name>bob_secondbob</name>
   <age>7</age>
   <country>Great_Britain</country>
 </component>

 <component>
   <name>bam</name>
   <age>7</age>
   <country>Great_Britain</country>
 </component>

第二种变体match range operator and named capture groups

use strict;
use warnings;
use feature 'say';

my(@data,%component);
my @fields = qw/name age country/;

while( <DATA> ) {
    chomp;
    if( /<component>/../<\/component>/ ) {
        $component{$+{tag}} = $+{val} if m!<(?<tag>.+?)>(?<val>.*?)</\g{tag}>!;
        if( /<\/component>/ ) {
            my %hash = %component;
            push @data, \%hash;
            %component = ();        
        }
    }
}

say join ',', @fields;
say join ',', $_->@{@fields} for @data;

__DATA__


 <component>
   <name>bob</name>
   <age>7</age>
   <country>Great_Britain</country>
 </component>

 <component>
   <name>bob_secondbob</name>
   <age>7</age>
   <country>Great_Britain</country>
 </component>

 <component>
   <name>bam</name>
   <age>7</age>
   <country>Great_Britain</country>
 </component>

输出

name,age,country
bob,7,Great_Britain
bob_secondbob,7,Great_Britain
bam,7,Great_Britain

您仍应使用适当的 XML 解析器。它将更快更简单更少error-prone更多可读性更易于维护,等等。只需从文件中提取 XML 位并从那里使用正常方法。

出于同样的原因,请使用合适的 CSV 生成器,而不是推出自己的生成器。

use Text::CSV_XS qw( );
use XML::LibXML  qw( );

my $xml_parser = XML::LibXML->new()
my $csv = Text::CSV_XS->new({ auto_diag => 2, binary => 1 });

$csv->say(\*STDOUT, [qw( name age country )]);

my $in_xml = 0;
my $xml;
while (
   $in_xml ||= /<component\b/;
   $xml .= $_ if $in_xml;
   if (/<\/component\b/) {
      my $doc = $xml_parser->parse_string($xml);
      my $name    = $doc->findvalue("/component/name");
      my $age     = $doc->findvalue("/component/age");
      my $country = $doc->findvalue("/component/country");
      $csv->say(\*STDOUT, [ $name, $age, $country ]);

      $in_xml = 0;
      $xml = undef;
   }
}

解析 XML 的替代方法:

my $doc = $xml_parser->parse_string($xml);
my %rec;
$rec{ $_->nodeName } = $_->textContent()
   for $doc->findnodes("/component/*");

$csv->say(\*STDOUT, [ @rec{qw( name age country )} ]);