Могу ли я использовать Text :: CSV_XS для разбора строки в формате csv без записи на диск? - PullRequest
8 голосов
/ 23 марта 2020

Я получаю «CSV-файл» от поставщика (используя его API), но они просто извергают все это в своем ответе. Это не было бы серьезной проблемой, за исключением того, что, конечно, некоторые из этих надоедливых людей вводили данные и добавляли такие «особенности», как разрывы строк. Сейчас я создаю файл для необработанных данных, а затем снова открываю его для чтения данных:

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 (многострочная строка), как если бы это был файл. Но я рисую полный пробел о том, как это сделать. Указатель будет принята с благодарностью. Спасибо, Пол

Ответы [ 3 ]

6 голосов
/ 23 марта 2020

Вы можете использовать строковый дескриптор файла:

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) ) { ... }
5 голосов
/ 24 марта 2020

Да, вы можете использовать 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 работает, но не с этим).

Я не знаю, почему это не доступно в интерфейсе OO (или возможно, но не задокументировано).


Хотя приведенное выше анализирует строку непосредственно в соответствии с запросом, можно также уменьшить «неэлегантный» аспект в вашем примере, записав содержимое непосредственно в файл при его получении, что большинство библиотек поддерживают, например, с опцией :content_file в LWP :: UserAgent :: get метод.

Позвольте мне также отметить, что большую часть времени вы хотите, чтобы библиотека декодировала контент, поэтому LWP::UA будет использовать decoded_content (см. HTTP :: Response ).

3 голосов
/ 24 марта 2020

Я подготовил этот пример с помощью Mojo :: UserAgent . Для ввода CSV я использовал различные наборы данных из NY C Open Data . Это также появится в следующем обновлении для веб-клиентов Mojo .

. Я создаю запрос, не отправляя его сразу, и это дает мне объект транзакции $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];
            }
        };
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...