使用 Perl 将 CSV 文件转换为 XML

Converting CSV file to XML with Perl

我正在尝试解析 CSV 文件并将其转换为 XML。 .csv 文件由一个条目列表组成,条目之间以逗号分隔。因此,两个示例条目如下所示:

License,Date,Mileage
04-nh-pd,17-11-2020,30000
19-tg-jr,17-11-2020,36000

预期输出:

<?xml version="1.0" encoding="UTF-8" ?><ns1:ImportObjectMileage xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ns1="http://www.co-maker.nl/LeaseOffice/types">
<ns1:ObjectMileage><ns1:object_code>04-nh-pd</ns1:object_code><ns1:mileagedate>17-11-2020</ns1:mileagedate><ns1:mileage>30000</ns1:mileage><ns1:icode_mileagecause_ecode>KEUR</ns1:icode_mileagecause_ecode></ns1:ObjectMileage>
<ns1:ObjectMileage><ns1:object_code>19-tg-jr</ns1:object_code><ns1:mileagedate>17-11-2020</ns1:mileagedate><ns1:mileage>36000</ns1:mileage><ns1:icode_mileagecause_ecode>KEUR</ns1:icode_mileagecause_ecode></ns1:ObjectMileage>

到目前为止我的代码:

#!perl
use strict;
# Open the ch2_xml_users.csv file for input
open(CSV_FILE, "ch2_xmlusers.csv") || die "Can't open file: $!";

# Open the ch2_xmlusers.xml file for output
open(XML_FILE, ">ch2_xmlusers.xml") || die "Can't open file: $!";

# Print the initial XML header and the root element
print XML_FILE '<?xml version="1.0" encoding="UTF-8" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ns1="http://www.co-maker.nl/LeaseOffice/types';


my $kenteken = "";
# The while loop to traverse through each line in users.csv
while(<CSV_FILE>) {
    chomp; # Delete the new line char for each line
    # Split each field, on the comma delimiter, into an array
    my @fields = split(/,/, $_);
  $kenteken .= <<"EOF";
    <ns1:ObjectMileage><ns1:object_code>$fields[0]</ns1:object_code><ns1:mileagedate>$fields[1]</ns1:mileagedate><ns1:mileage>$fields[2]</ns1:mileage><ns1:icode_mileagecause_ecode>$fields[3]</ns1:icode_mileagecause_ecode></ns1:ObjectMileage>
    
EOF
}

print XML_FILE "\n".$kenteken."\n";


# Close all open files
close CSV_FILE;
close XML_FILE;
 

到目前为止我的输出:

<?xml version="1.0" encoding="UTF-8" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ns1="http://www.co-maker.nl/LeaseOffice/types
    <ns1:ObjectMileage><ns1:object_code>License</ns1:object_code><ns1:mileagedate>Date</ns1:mileagedate><ns1:mileage>Mileage</ns1:mileage><ns1:icode_mileagecause_ecode></ns1:icode_mileagecause_ecode></ns1:ObjectMileage>
    
    <ns1:ObjectMileage><ns1:object_code>04-nh-pd</ns1:object_code><ns1:mileagedate>17-11-2020</ns1:mileagedate><ns1:mileage>30000</ns1:mileage><ns1:icode_mileagecause_ecode></ns1:icode_mileagecause_ecode></ns1:ObjectMileage>
    
    <ns1:ObjectMileage><ns1:object_code>19-tg-jr</ns1:object_code><ns1:mileagedate>17-11-2020</ns1:mileagedate><ns1:mileage>36000</ns1:mileage><ns1:icode_mileagecause_ecode></ns1:icode_mileagecause_ecode></ns1:ObjectMileage>
    
    <ns1:ObjectMileage><ns1:object_code></ns1:object_code><ns1:mileagedate></ns1:mileagedate><ns1:mileage></ns1:mileage><ns1:icode_mileagecause_ecode></ns1:icode_mileagecause_ecode></ns1:ObjectMileage>
    
    <ns1:ObjectMileage><ns1:object_code></ns1:object_code><ns1:mileagedate></ns1:mileagedate><ns1:mileage></ns1:mileage><ns1:icode_mileagecause_ecode></ns1:icode_mileagecause_ecode></ns1:ObjectMileage>
    


header 下面的第一行和最后两行不应显示在输出中。 数据之间的空行也不正确。有人可以帮我写剧本吗?

您在 heredoc 中添加了 2 个换行符,打印时又添加了 2 个。如果您不想要那么多换行符,为什么不删除其中的一些呢?

至于你的输出,你可能会考虑在循环内声明变量,然后直接打印:

while (<>) {
    ...
    my $kenteken = ....
    print ...
}

这样每一行新的输入都会得到一个新的临时变量。

但是,既然可以跳过它,为什么还要使用临时变量呢?例如,您可以像这样使用 printf

printf XML_FILE "<ns1:ObjectMileage><ns1:object_code>%s</ns1:object_code><ns1:mileagedate>%s</ns1:mileagedate><ns1:mileage>%s</ns1:mileage><ns1:icode_mileagecause_ecode>%s</ns1:icode_mileagecause_ecode></ns1:ObjectMileage>\n", @fields;

用法是 printf "%s", $var,其中 %s 表示 $var 提供的字符串的占位符。请注意,我在末尾添加了换行符 \n,这通常是打印一行的方式。

末尾没有值的两行可能是输入文件中的空行。如果您在代码中使用了 use warnings,您就会知道这一点。因为你没有,所以你没有收到关于输入中空行的警告,它看起来像这样:

Use of uninitialized value in concatenation (.) or string at ...

您可以检查输入文件行并跳过空行来避免这种情况。例如:

while (<>) {
    next unless /\S/;   # skip lines without non-whitespace characters

那么……说了这么多,这不是你应该做的。您应该(可能)使用 csv 模块,例如 Text::CSV to read your input file, and then use an xml-module to print it. I am not terribly familiar with these, but if you google, you should find some recommendations. I have heard some recommend XML::LibXML。不过,不要问关于模块的建议问题,因为这不是 Whosebug 的主题。如评论中所述,像您所做的那样打印简单的 XML 可能没问题。

我已对您的脚本进行了以下更改,看看是否适合您。

  1. 始终使用词法文件句柄进行文件操作。
  2. xml header 行结束于 ..types">
  3. 有几种方法可以跳过 CSV 文件的 header:
    3.1 通过将一行读入循环上方的空上下文来摆脱 header 的模式匹配(如评论中提到的@simbabque)。
    3.2 如果CSV文件line匹配(=~)和License,Date,Mileage,则跳过next语句的行。
  4. 不是一个接一个地连接 kentekens,而是在 csv 读取操作本身时用必填字段写入行内容。

下面是修改后的脚本:

use strict; use warnings;

no warnings 'uninitialized';

open my $CSV_FILE, "<", "ch2_xmlusers.csv" or die "Cannot open a file: $!";
open my $XML_FILE, ">", "ch2_xmlusers.xml" or die "Cannot open a file: $!";

print $XML_FILE '<?xml version="1.0" encoding="UTF-8" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ns1="http://www.co-maker.nl/LeaseOffice/types">'."\n";

my $kenteken = "";
my $csv_header = <$CSV_FILE>;

while(<$CSV_FILE>) {
    chomp; 
    my @fields = split ',', $_;
    $kenteken = <<"EOF";
<ns1:ObjectMileage><ns1:object_code>$fields[0]</ns1:object_code><ns1:mileagedate>$fields[1]</ns1:mileagedate><ns1:mileage>$fields[2]</ns1:mileage><ns1:icode_mileagecause_ecode>$fields[3]</ns1:icode_mileagecause_ecode></ns1:ObjectMileage>   
EOF
    print $XML_FILE $kenteken;
}
close $CSV_FILE;
close $XML_FILE;

结果:

<?xml version="1.0" encoding="UTF-8" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ns1="http://www.co-maker.nl/LeaseOffice/types">
<ns1:ObjectMileage><ns1:object_code>04-nh-pd</ns1:object_code><ns1:mileagedate>17-11-2020</ns1:mileagedate><ns1:mileage>30000
</ns1:mileage><ns1:icode_mileagecause_ecode></ns1:icode_mileagecause_ecode></ns1:ObjectMileage>   
<ns1:ObjectMileage><ns1:object_code>19-tg-jr</ns1:object_code><ns1:mileagedate>17-11-2020</ns1:mileagedate><ns1:mileage>36000</ns1:mileage><ns1:icode_mileagecause_ecode></ns1:icode_mileagecause_ecode></ns1:ObjectMileage>