¿Puedo usar Text :: CSV_XS para analizar una cadena de formato csv sin escribirla en el disco?

8

Recibo un "archivo csv" de un proveedor (usando su API), pero lo que hacen es arrojar todo en su respuesta. No sería un problema significativo, excepto que, por supuesto, algunos de esos molestos humanos ingresaron los datos y agregaron "características" como saltos de línea. Lo que estoy haciendo ahora es crear un archivo para los datos sin procesar y luego volver a abrirlo para leer los datos:

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 alguna manera esto parece ... poco elegante. Parece que debería poder leer los datos del $ response-> content (cadena multilínea) como si fuera un archivo. Pero estoy dibujando un espacio en blanco sobre cómo hacer esto. Un puntero sería muy apreciado. Gracias paul

Paul RN
fuente

Respuestas:

6

Podría usar un identificador de archivo de cadena:

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
fuente
3
Este es uno de mis trucos favoritos en Perl, y escribo bastante sobre esto en Programación efectiva de Perl . Tratar muchas cosas como un controlador de archivos significa que tiene una interfaz más fácil y familiar. Va a la inversa también; puede escribir en un identificador de archivo pero que aparezca en una cadena.
Brian D Foy
3
Sí, bueno, yo también lo uso, uno no debe olvidar que no es un archivo apropiado para no tener problemas; mira esta publicación por ejemplo.
zdim
1
¡Bueno, gracias! Eso era exactamente lo que estaba buscando, pero no del todo. Ya no puedo recordar exactamente qué combinaciones había intentado, pero evidentemente estaba cerca pero no tenía la sintaxis correcta.
Paul RN
5

Sí, puede usar Text :: CSV_XS en una cadena, a través de su interfaz 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;    

Tenga en cuenta que esto realmente necesita Text::CSV_XS(normalmente Text :: CSV funciona pero no con esto).

No sé por qué esto no está disponible en la interfaz OO (o tal vez sí, pero no está documentado).


Si bien lo anterior analiza la cadena directamente como se le solicitó, también se puede disminuir el aspecto "poco elegante" en su ejemplo al escribir contenido directamente en un archivo a medida que se adquiere, lo que admite la mayoría de las bibliotecas con la :content_fileopción en LWP :: UserAgent :: get método .

Permítanme también señalar que la mayoría de las veces desea que la biblioteca decodifique el contenido, para LWP::UAque lo use decoded_content(vea HTTP :: Response ).

zdim
fuente
3

He preparado este ejemplo con Mojo :: UserAgent . Para la entrada CSV utilicé varios conjuntos de datos de NYC Open Data . Esto también aparecerá en la próxima actualización para Mojo Web Clients .

Construyo la solicitud sin realizar la consulta de inmediato, y eso me da el objeto de transacción, $tx. Entonces puedo reemplazar el readevento para poder enviar inmediatamente las líneas a 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);

Eso no es tan bueno como me gustaría que sea porque todos los datos aún se muestran en el búfer. Esto es un poco más atractivo, pero es frágil en las formas que noto en los comentarios. Soy demasiado vago en este momento para mejorarlo porque eso se vuelve muy rápido cuando te das cuenta de que tienes suficientes datos para procesar un registro. Mi código particular no es tan importante como la idea de que puedes hacer lo que quieras mientras el transactor lee los datos y los pasa al controlador de contenido:

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
fuente