Kann ich Text :: CSV_XS verwenden, um eine Zeichenfolge im CSV-Format zu analysieren, ohne sie auf die Festplatte zu schreiben?

8

Ich bekomme eine "CSV-Datei" von einem Anbieter (unter Verwendung seiner API), aber was sie tun, ist nur das Ganze in ihre Antwort zu spucken. Es wäre kein bedeutendes Problem, außer dass natürlich einige dieser nervigen Menschen die Daten eingegeben und "Merkmale" wie Zeilenumbrüche eingegeben haben. Was ich jetzt mache, ist eine Datei für die Rohdaten zu erstellen und sie dann erneut zu öffnen, um die Daten zu lesen:

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

Irgendwie scheint das ... unelegant. Es scheint, dass ich in der Lage sein sollte, die Daten aus dem $ response-> content (mehrzeiliger String) so zu lesen, als wäre es eine Datei. Aber ich zeichne eine totale Lücke, wie das geht. Ein Zeiger wäre sehr dankbar. Danke, Paul

Paul RN
quelle

Antworten:

6

Sie könnten ein String-Dateihandle verwenden:

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
quelle
3
Dies ist einer meiner Lieblingstricks in Perl, und ich schreibe ziemlich viel darüber in Effective Perl Programming . Wenn Sie viele Dinge als Dateihandle behandeln, haben Sie eine einfachere und vertraute Oberfläche. Es geht auch in die andere Richtung; Sie können in ein Dateihandle schreiben, es aber in einer Zeichenfolge anzeigen lassen.
Brian D Foy
3
Ja, schön, ich benutze das auch - man sollte einfach nicht vergessen, dass es kein richtiges Dateihandle ist, um nicht in Schwierigkeiten zu geraten; siehe diesen Beitrag zum Beispiel.
Zdim
1
Nun, danke! Genau das habe ich gesucht, aber nicht ganz verstanden. Ich kann mich nicht mehr genau erinnern, welche Kombinationen ich versucht hatte, aber ich war offensichtlich nah dran, aber ich habe die Syntax nicht ganz richtig verstanden.
Paul RN
5

Ja, Sie können Text :: CSV_XS für einen String über dessen Funktionsschnittstelle verwenden

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;    

Beachten Sie, dass dies tatsächlich erforderlich ist Text::CSV_XS(normalerweise funktioniert Text :: CSV, aber nicht damit).

Ich weiß nicht, warum dies in der OO-Schnittstelle nicht verfügbar ist (oder vielleicht aber nicht dokumentiert ist).


Während das Obige die Zeichenfolge direkt wie gewünscht analysiert, kann man auch den "uneleganten" Aspekt in Ihrem Beispiel verringern, indem Sie Inhalte direkt in eine Datei schreiben, während diese erfasst werden. Dies wird von den meisten Bibliotheken mit der :content_fileOption in der LWP :: UserAgent :: get- Methode unterstützt.

Lassen Sie mich auch beachten, dass die Bibliothek die meiste Zeit Inhalte dekodieren soll, um sie LWP::UAzu verwenden decoded_content(siehe HTTP :: Response ).

zdim
quelle
3

Ich habe dieses Beispiel mit Mojo :: UserAgent erstellt . Für die CSV-Eingabe habe ich verschiedene Datensätze aus den NYC Open Data verwendet . Dies wird auch im nächsten Update für Mojo Web Clients angezeigt .

Ich erstelle die Anfrage, ohne sie sofort zu stellen, und das gibt mir das Transaktionsobjekt $tx. Ich kann das readEreignis dann ersetzen , damit ich die Zeilen sofort an Text :: CSV_XS senden kann :

#!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);

Das ist nicht so schön, wie ich es gerne hätte, weil alle Daten immer noch im Puffer angezeigt werden. Dies ist etwas ansprechender, aber es ist fragil in der Art, wie ich es in den Kommentaren notiere. Ich bin im Moment zu faul, um es besser zu machen, weil das sehr schnell haarig wird, wenn Sie herausfinden, wann Sie genug Daten haben, um einen Datensatz zu verarbeiten. Mein spezieller Code ist nicht so wichtig wie die Idee, dass Sie tun können, was Sie möchten, während der Transaktor Daten liest und an den Inhaltshandler weitergibt:

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
quelle