【问题标题】:Iterating through CSV and creating an XML file遍历 CSV 并创建 XML 文件
【发布时间】:2016-10-30 09:11:30
【问题描述】:

我正在尝试在 Perl 中解析 CSV 文件并将某些列的信息粘贴到 XML 文件中。我从来没有在 Perl 中做过任何事情,我的想法是将数据存储到一个数组中,然后在构建它时将信息从数组中提取出来。

我确定我做错了几件事,因为我没有得到我期望的值,而是看起来像内存中的数组地址(这里是一个例子:ARRAY(0x35e9360)

有人可以帮助我并指出更好的解决方案吗?

这里是有问题的代码:

use Text::CSV;
use utf8;
use XML::Simple qw(XMLout);
use XML::Twig;
use File::Slurp;
use Encode;

&buildXML();

my $csv = Text::CSV->new( { binary => 1 } )    # should set binary attribute.
        or die "Cannot use CSV: " . Text::CSV->error_diag();

$csv = Text::CSV->new( { sep_char => '|' } );
$csv = Text::CSV_XS->new( { allow_loose_quotes => 1 } );

my $t = XML::Twig->new( pretty_print => indented );
$t->parsefile('output.xml');

$out_file = "output.xml";
open( my $fh_out, '>>', $out_file ) or die "unable to open $out_file for writing: $!";

my $root = $t->root;                           #get the root

open my $fh, "<:encoding(utf8)", "b.txt" or die "text.txt: $!";

while ( my $row = $csv->getline($fh) ) {

    my @rows = $row;

    $builds = $root->first_child();            # get the builds node
    $xcr    = $builds->first_child();          #get the xcr node

    my $xcrCopy = $xcr->copy();                #copy the xcr node
    $xcrCopy->paste( after, $xcr );            #paste the xcr node

    $xcr->set_att( id => "@rows[0]" );
    print {$fh_out} $t->sprint();
}

$csv->eof or $csv->error_diag();

这是一个测试文件:

ID|Name|Pos
1|a|265
2|b|950
3|c|23
4|d|798
5|e|826
6|f|935
7|g|852
8|h|236
9|i|642

这是由 buildXML() 子构建的 XML。

<?xml version='1.0' standalone='yes'?>
<project>
  <builds>
    <xcr id="" name="" pos="" />          
  </builds>
</project>

【问题讨论】:

  • getline 方法返回一个 arrayref ——正如您所说,您看到的是对数组的引用。您需要my @rows = @$row,将其取消引用到一个数组中。至于其余的,你能发一个我可以测试的文件(b.txt)吗?
  • 还需要output.xml,除非您打算从头开始编写它(但它的代码是错误的)。
  • 在开始测试之前,您确实编写了太多代码,最终产生了许多混淆了整个程序的混乱想法和试用代码。您应该首先编写几行从 CSV 中提取值的行,仅此而已。此外,always use strictuse warnings 'all' 在每个 Perl 程序的顶部。调用子例程时不要使用 & 符号 &amp;:只要 buildXML() 是正确的,如果它告诉你,你用来学习 Perl 的任何资源都非常过时。
  • @zdim 我正在从头开始构建它。有一个子 buildXML。它构建了一个非常简单的结构。我会将其添加到我原来的问题中。

标签: arrays xml perl csv


【解决方案1】:

Text::CSVgetline 方法返回一个数组引用

它使用 $io->getline() 从 IO 对象 $io 中读取一行,并将该行解析为一个数组 ref。

ARRAY(0x35e9360) 确实是您在打印出数组引用时得到的。这很常见,许多解析器通常会返回对一行数组的引用。所以你需要取消引用,通常是@{$arrayref},但在这种情况下没有歧义,可以去掉花饰,@$arrayref

use warnings;
use strict;
use Text::CSV_XS;
use XML::Twig;

my $csv = Text::CSV_XS->new (
    { binary => 1, sep_char => '|',  allow_loose_quotes => 1 }
) or die "Cannot use CSV: " . Text::CSV->error_diag();

my $t = XML::Twig->new(pretty_print => 'indented');
$t->parsefile('output.xml');
my $out_file = 'output.xml';
open my $fh_out, '>>', $out_file  or die "Can't open $out_file for append: $!";
my $root = $t->root;

my $file = 'b.txt';
open my $fh, "<:encoding(UTF-8)", $file  or die "Can't open $file: $!";

while (my $rowref = $csv->getline($fh)) {
    #my @cols = @$rowref;
    #print "@cols\n";

    my $builds = $root->first_child();  # get the builds node
    my $xcr = $builds->first_child();   # get the xcr node
    my $xcrCopy = $xcr->copy();         # copy the xcr node
    $xcrCopy->paste('after', $xcr);     # paste the xcr node
    $xcr->set_att(id => $rowref->[0]);  # or $cols[0];

    print $fh_out $t->sprint();
}

这会为 CSV 文件打印(当 @cols 及其打印未注释时)

身份证姓名位置 1个265 2 b 950 ...

所以我们已经读取文件OK了。

XML 处理是从问题中复制的,使用 CSV 值的部分除外。我们取当前行的第一个元素,即$rowref-&gt;[0],因为$rowref 是一个引用。 (或者使用取消引用数组中的元素$cols[0]。)

我不知道预期的输出,但它是根据模板构建的,并且对于这段代码来说似乎没问题。


注意。数组的单个元素是标量,因此它带有 $ - 所以, $cols[0]。如果您要提取多个列,您可以使用 数组切片,在这种情况下,结果是一个数组,因此它需要 @,例如 @cols[0,2] 是一个包含第一个和第三个的数组元素。然后可以将其分配给一个列表,例如my ($c1, $c3) = @cols[0,2];

【讨论】:

  • 谢谢你,但我现在得到的是整行,而不是我想要得到的一个值。
  • @UsefulUserName 我已添加到代码中,以获取 CSV 的所有元素并将它们打印出来,这样您就可以看到它是如何打包的。至于 XML,很高兴有output.xml
  • @UsefulUserName 感谢您提供 xml 模板。我已将 XML 部分添加到代码中,现在它正在处理整个事情。它确实构建了output.xml,但我不知道它是否正是你想要的(看起来很合理)。
  • @UsefulUserName 我确定我在这里向您发表了评论,但现在它已经消失了(?)。抱歉,如果这重复 - 上面的代码已被大量清理。一方面,您的代码调用new 两次 并且不应该这样做。现在这要简单得多,因为它应该是。至于 XML,它只执行您在问题中已有的内容。
  • 这对我来说很好,我正在使用这个解决方案,因为它给我留下了一些我正在处理的其他事情需要的选项。
【解决方案2】:

这个程序似乎按照你的要求做

链接:

在对您的代码进行逆向工程以发现您的目标之后,我发现这确实是一个相当简单的问题。如果您在为 CSV 文件中的每一行添加一个新的 xcr 元素以及与列对应的属性方面解释了您的意图,那将会很有帮助

您可能根本不需要 XML 模板文件,或者只是具有空属性的模板 xcr 元素是多余的?我还想知道您是否想跳过 CSV 文件中的标题行?这些更改是微不足道的,但我已将代码保持在最简单的状态

use utf8;
use strict;
use warnings 'all';
use autodie;

use Text::CSV;
use XML::Twig;
use Encode;

use constant XML_FILE => 'output.xml';
use constant CSV_FILE => 'b.txt';

build_xml(XML_FILE);

my $csv = Text::CSV->new( {
    sep_char           => '|',
    binary             => 1,
    allow_loose_quotes => 1,   # This is brought forward. Probably unnecessary
} );

my $t = XML::Twig->new(
    pretty_print => 'indented',
);

$t->parsefile(XML_FILE);
my ($xcr) = $t->findnodes('/project/builds/xcr');

open my $fh, '<:encoding(utf8)', CSV_FILE;

while ( my $row = $csv->getline($fh) ) {

    my ($id, $name, $pos) = @$row;

    my $xcr_copy = $xcr->copy;
    $xcr_copy->set_att( id => $id, name => $name, pos => $pos );
    $xcr_copy->paste( last_child => $xcr->parent );
}

$t->print;


sub build_xml {

    open my $fh, '>', shift;

    print $fh <<__END_XML__;
<?xml version='1.0' standalone='yes'?>
<project>
  <builds>
    <xcr id="" name="" pos="" />          
  </builds>
</project>
__END_XML__

}

输出

<?xml version="1.0" standalone="yes"?>
<project>
  <builds>
    <xcr id="" name="" pos=""/>
    <xcr id="ID" name="Name" pos="Pos"/>
    <xcr id="1" name="a" pos="265"/>
    <xcr id="2" name="b" pos="950"/>
    <xcr id="3" name="c" pos="23"/>
    <xcr id="4" name="d" pos="798"/>
    <xcr id="5" name="e" pos="826"/>
    <xcr id="6" name="f" pos="935"/>
    <xcr id="7" name="g" pos="852"/>
    <xcr id="8" name="h" pos="236"/>
    <xcr id="9" name="i" pos="642"/>
  </builds>
</project>



在阅读您的评论后(应该将此类内容编辑到问题中)说 “我正在从头开始构建 [XML 数据]。有一个子 buildXML” 我认为这更有可能成为你所需要的。使用XML::Twig,最简单的方法是解析一些 XML 文本,而不是创建和链接单个 XML::Twig::Elt 对象

$t 对象以根本没有 xcr 对象开头。它们都是通过XML::Twig::Elt-&gt;new 创建并粘贴为builds 元素的last_child

require v5.14.1;  # For autodie

use utf8;
use strict;
use warnings 'all';
use autodie;

use Text::CSV;
use XML::Twig;
use Encode;

use constant XML_FILE => 'output.xml';
use constant CSV_FILE => 'b.txt';

my $t = XML::Twig->new(
    pretty_print => 'indented',
);

$t->parse(<<END_XML);
<project>
  <builds/>
</project>
END_XML

my ($builds) = $t->findnodes('/project/builds');


my $csv = Text::CSV->new( {
    sep_char => '|',
    binary => 1,
    allow_loose_quotes => 1,
} );

{
    open my $fh, '<:encoding(utf8)', CSV_FILE;
    <$fh>; # Drop the header line

    while ( my $row = $csv->getline($fh) ) {

        my ($id, $name, $pos) = @$row;

        my $xcr = XML::Twig::Elt->new(xcr => {
            id   => $id,
            name => $name,
            pos  => $pos
        });

        $xcr->paste( last_child => $builds );
    }
}

open my $fh, '>encoding(utf-8)', XML_FILE;
$t->set_output_encoding('UTF-8');
$t->print($fh, 'indented');

输出

<?xml version="1.0" encoding="UTF-8"?><project>
  <builds>
    <xcr id="1" name="a" pos="265"/>
    <xcr id="2" name="b" pos="950"/>
    <xcr id="3" name="c" pos="23"/>
    <xcr id="4" name="d" pos="798"/>
    <xcr id="5" name="e" pos="826"/>
    <xcr id="6" name="f" pos="935"/>
    <xcr id="7" name="g" pos="852"/>
    <xcr id="8" name="h" pos="236"/>
    <xcr id="9" name="i" pos="642"/>
  </builds>
</project>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-09-29
    • 2015-09-11
    • 1970-01-01
    • 2016-03-13
    • 2021-08-15
    • 2013-06-29
    • 1970-01-01
    相关资源
    最近更新 更多