Posso usar Text :: CSV_XS para analisar uma seqüência de caracteres no formato csv sem gravá-la no disco?

8

Estou recebendo um "arquivo csv" de um fornecedor (usando sua API), mas o que eles fazem é apenas espalhar a coisa toda em sua resposta. Não seria um problema significativo, exceto que, é claro, alguns daqueles humanos irritantes entraram nos dados e colocaram "recursos", como quebras de linha. O que estou fazendo agora é criar um arquivo para os dados brutos e reabri-lo para ler os dados:

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) ) { ...

De alguma forma isso parece ... deselegante. Parece que eu deveria ser capaz de ler os dados do $ response-> content (cadeia de linhas multilinhas) como se fosse um arquivo. Mas estou desenhando um espaço em branco total sobre como fazer isso. Um ponteiro seria muito apreciado. Obrigado Paul

Paul RN
fonte

Respostas:

6

Você pode usar uma string filehandle:

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) ) { ... }
GMB
fonte
3
Este é um dos meus truques favoritos no Perl, e escrevo bastante sobre isso na Programação Efetiva do Perl . Tratar muitas coisas como um manipulador de arquivos significa que você tem uma interface mais fácil e familiar. Também vai para o outro lado; você pode gravar em um identificador de arquivo, mas fazer com que ele apareça em uma sequência.
brian d foy 23/03
3
Sim, legal, eu também uso isso - não se deve esquecer que não é um manuseio de arquivo adequado, para não ter problemas; veja este post por exemplo.
zdim 24/03
1
Bem, obrigada! Era exatamente o que eu estava procurando, mas não consegui. Não me lembro mais exatamente de quais combinações eu havia tentado, mas estava evidentemente próximo, mas não estava conseguindo a sintaxe correta.
Paul RN
5

Sim, você pode usar Text :: CSV_XS em uma string, por meio de sua interface funcional

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;    

Observe que isso realmente precisa Text::CSV_XS(normalmente Text :: CSV funciona, mas não com isso).

Não sei por que isso não está disponível na interface OO (ou talvez esteja, mas não esteja documentado).


Enquanto o acima analisa a string diretamente conforme solicitado, também é possível diminuir o aspecto "deselegante" no seu exemplo, escrevendo o conteúdo diretamente em um arquivo à medida que ele é adquirido, com o que a maioria das bibliotecas suporta, como a :content_fileopção no método LWP :: UserAgent :: get .

Deixe-me observar também que na maioria das vezes você deseja que a biblioteca decodifique o conteúdo, para LWP::UAusá-lo decoded_content(consulte HTTP :: Response ).

zdim
fonte
3

Eu preparei este exemplo com o Mojo :: UserAgent . Para a entrada CSV, usei vários conjuntos de dados do NYC Open Data . Isso também aparecerá na próxima atualização para Mojo Web Clients .

Eu construo a solicitação sem fazer a solicitação imediatamente, e isso me dá o objeto de transação $tx,. Posso substituir o readevento para enviar imediatamente as linhas para 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);

Isso não é tão bom quanto eu gostaria, porque todos os dados ainda aparecem no buffer. Isso é um pouco mais atraente, mas é frágil da maneira que observo nos comentários. No momento, sou muito preguiçoso para melhorar, porque isso fica muito rápido quando você descobre que possui dados suficientes para processar um registro. Meu código específico não é tão importante quanto a idéia de que você pode fazer o que quiser, pois o transator lê dados e os passa para o manipulador de conteúdo:

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];
            }
        };
    }
brian d foy
fonte