我能使用Text::CSV_XS解析csv格式的字符串而不将其写入磁盘吗?

9

我从供应商那里(使用他们的API)获取一个“csv文件”,但他们所做的就是将整个文件喷到其响应中。这本身不是很大的问题,除非有些顽固的人类输入数据并添加了“特性”比如换行符。现在我正在为原始数据创建一个文件,然后重新打开它以读取数据:

open RAW, ">", "$rawfile" or die "ERROR: Could not open $rawfile for write: $! \n";
print RAW $response->content;
close RAW;

my $csv = Text::CSV_XS->new({ binary=>1,always_quote=>1,eol=>$/ });
open my $fh, "<", "$rawfile" or die "ERROR: Could not open $rawfile for read: $! \n";

while ( $line = $csv->getline ($fh) ) { ...

以某种方式来看,这似乎不太优雅。看起来我应该能够像读取文件一样从$response->content(多行字符串)中读取数据。但是我完全不知道如何做到这一点。希望能得到指导。

谢谢,Paul

3个回答

7
您可以使用字符串文件句柄:
my $data = $response->content;
open my $fh, "<", \$data or croak "unable to open string filehandle : $!";
my $csv = Text::CSV_XS->new({ binary=>1,always_quote=>1,eol=>$/ });
while ( $line = $csv->getline ($fh) ) { ... }

4
这是我在Perl中最喜欢的技巧之一,我在《Effective Perl Programming》一书中写了很多相关内容。将许多事物视为文件句柄意味着您拥有更简单、更熟悉的界面。这个技巧也可以反过来用;您可以将输出写入文件句柄,但让它显示在字符串中。 - brian d foy
3
是的,不错,我也用过 -- 只是不能忘记它不是一个合适的文件句柄,以免遇到麻烦;例如,请参见这篇帖子 - zdim
1
谢谢!这正是我一直在寻找但没有完全掌握的内容。虽然我已经无法回忆起我尝试了哪些组合,但显然我已经接近正确的语法了,只是还没有掌握到位。 - Paul R N

4

是的,你可以通过 Text::CSV_XS 的函数接口在字符串上使用它。

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

use Text::CSV_XS qw(csv);  # must use _XS version

my $csv = qq(a,line\nand,another);

my $aoa = csv(in => \$csv) 
    or die Text::CSV->error_diag; 

say "@$_" for @aoa;    

请注意,确实需要使用 Text::CSV_XS(通常情况下Text::CSV 可以工作,但是不能用于此)。
我不知道为什么这个接口在面向对象的界面中无法使用(或者可能可以使用,但没有记录)。
虽然上面的代码直接解析字符串,但是也可以通过像LWP::UserAgent::get方法中的:content_file选项一样将内容直接写入文件来减少示例中"不优美"的部分。 另外需要注意的是,大多数情况下您希望库对内容进行解码,因此对于LWP::UA 应该使用 decoded_content(请参阅HTTP::Response)。

3
我用Mojo::UserAgent编写了这个示例。对于CSV输入,我使用了来自NYC Open Data的各种数据集。这也将出现在Mojo Web Clients的下一个更新中。
我构建了请求,但并没有立即发出请求,这给了我事务对象$tx。然后我可以替换read事件,以便我可以立即将行发送到Text::CSV_XS中。
#!perl

use v5.10;
use Mojo::UserAgent;

my $ua = Mojo::UserAgent->new;

my $url = ...;
my $tx = $ua->build_tx( GET => $url );

$tx->res->content->unsubscribe('read')->on(read => sub {
    state $csv = do {
        require Text::CSV_XS;
        Text::CSV_XS->new;
        };
    state $buffer;
    state $reader = do {
        open my $r, '<:encoding(UTF-8)', \$buffer;
        $r;
        };

    my ($content, $bytes) = @_;
    $buffer .= $bytes;
    while (my $row = $csv->getline($reader) ) {
        say join ':', $row->@[2,4];
        }
    });

$tx = $ua->start($tx);

我希望这个方法更好一些,因为它不会把所有数据都显示在缓冲区中。不过,正如我在注释中所指出的那样,这种方法比较脆弱。此刻,我有点懒得再去改进他,因为当你想要处理一个记录时,很快就会变得非常困难。我的代码并不是最重要的,最重要的是你可以根据自己的需求来读取数据,并将其传递给内容处理器:

use v5.10;
use strict;
use warnings;
use feature qw(signatures);
no warnings qw(experimental::signatures);

use Mojo::UserAgent;

my $ua = Mojo::UserAgent->new;

my $url = ...;
my $tx = $ua->build_tx( GET => $url );

$tx->res->content
    ->unsubscribe('read')
    ->on( read => process_bytes_factory() );

$tx = $ua->start($tx);

sub process_bytes_factory {
    return sub ( $content, $bytes ) {
        state $csv = do {
            require Text::CSV_XS;
            Text::CSV_XS->new( { decode_utf8 => 1 } );
            };
        state $buffer = '';
        state $line_no = 0;

        $buffer .= $bytes;
        # fragile if the entire content does not end in a
        # newline (or whatever the line ending is)
        my $last_line_incomplete = $buffer !~ /\n\z/;

        # will not work if the format allows embedded newlines
        my @lines = split /\n/, $buffer;
        $buffer = pop @lines if $last_line_incomplete;

        foreach my $line ( @lines ) {
            my $status = $csv->parse($line);
            my @row = $csv->fields;
            say join ':', $line_no++, @row[2,4];
            }
        };
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接