Come posso analizzare CSV citato in Perl con un’espressione regolare?

Sto riscontrando alcuni problemi con l’analisi dei dati CSV con le virgolette. Il mio problema principale è con le virgolette all’interno di un campo. Nell’esempio seguente le righe 1 – 4 funzionano correttamente ma 5,6 e 7 no.

COLLOQ_TYPE,COLLOQ_NAME,COLLOQ_CODE,XDATA S,"BELT,FAN",003541547, S,"BELT V,FAN",000324244, S,SHROUD SPRING SCREW,000868265, S,"D" REL VALVE ASSY,000771881, S,"YBELT,"V"",000323030, S,"YBELT,'V'",000322933, 

Mi piacerebbe evitare Text :: CSV in quanto non è installato sul server di destinazione . Rendendosi conto che i CSV sono più complicati di quanto sembrino, sto usando una ricetta del ricettario Perl.

 sub parse_csv { my $text = shift; #record containg CSVs my @columns = (); push(@columns ,$+) while $text =~ m{ # The first part groups the phrase inside quotes "([^\"\\]*(?:\\.[^\"\\]*)*)",? | ([^,]+),? | , }gx; push(@columns ,undef) if substr($text, -1,1) eq ','; return @columns ; # list of vars that was comma separated. } 

Qualcuno ha un suggerimento per migliorare la regex per gestire i casi di cui sopra?

Per favore, prova ad usare CPAN

Non c’è motivo per cui non sia ansible scaricare una copia di Text :: CSV , o qualsiasi altra implementazione non basata su XS di un parser CSV e installarla nella tua directory locale, o in una directory lib / sub del tuo progetto in modo che sia installata lungo con il lancio dei tuoi progetti.

Se non riesci a memorizzare i file di testo nel tuo progetto, allora mi chiedo come stai codificando il tuo progetto.

http://novosial.org/perl/life-with-cpan/non-root/

Dovrebbe essere una buona guida su come ottenere questi in uno stato di lavoro a livello locale.

Non usare CPAN è davvero una ricetta per il disastro.

Considerare questo prima di provare a scrivere la propria implementazione CSV.

Testo: CSV contiene oltre un centinaio di righe di codice, compresi bug risolti e casi limite, e la riscrittura di questo da zero ti farà solo capire quanto può essere terribile il CSV.

nota: l’ho imparato nel modo più duro. Mi ci è voluto un giorno intero per ottenere un parser CSV funzionante in PHP prima di scoprire che ne era stato aggiunto uno in una versione successiva. È davvero qualcosa di terribile.

Puoi analizzare CSV usando Testo :: ParseWords che viene fornito con Perl.

 use Text::ParseWords; while () { chomp; my @f = quotewords ',', 0, $_; say join ":" => @f; } __DATA__ COLLOQ_TYPE,COLLOQ_NAME,COLLOQ_CODE,XDATA S,"BELT,FAN",003541547, S,"BELT V,FAN",000324244, S,SHROUD SPRING SCREW,000868265, S,"D" REL VALVE ASSY,000771881, S,"YBELT,"V"",000323030, S,"YBELT,'V'",000322933, 

che analizza correttamente il tuo CSV ….

 # => COLLOQ_TYPE:COLLOQ_NAME:COLLOQ_CODE:XDATA # => S:BELT,FAN:003541547: # => S:BELT V,FAN:000324244: # => S:SHROUD SPRING SCREW:000868265: # => S:D REL VALVE ASSY:000771881: # => S:YBELT,V:000323030: # => S:YBELT,'V':000322933: 

L’unico problema che ho riscontrato con Text :: ParseWords è quando le virgolette nidificate nei dati non vengono sfuggite correttamente. Tuttavia, si tratta di dati CSV mal costruiti che causerebbero problemi con la maggior parte dei parser CSV 😉

Quindi potresti accorgertene

 # S,"YBELT,"V"",000323030, 

è uscito come (cioè le virgolette sono cadute intorno alla “V”)

 # S:YBELT,V:000323030: 

tuttavia se è fuggito in questo modo

 # S,"YBELT,\"V\"",000323030, 

quindi le virgolette saranno mantenute

 # S:YBELT,"V":000323030: 

Funziona come un fascino

si presume che la linea sia separata da virgole con embeded,

my @columns = Testo :: ParseWords :: parse_line (‘,’, 0, $ line);

testato; lavoro:-

 $_.=','; # fake an ending delimiter while($_=~/"((?:""|[^"])*)",|([^,]*),/g) { $cell=defined($1) ? $1:$2; $cell=~s/""/"/g; print "$cell\n"; } # The regexp strategy is as follows: # First - we attempt a match on any quoted part starting the CSV line:- # "((?:""|[^"])*)", # It must start with a quote, and end with a quote followed by a comma, and is allowed to contain either doublequotes - "" - or anything except a sinlge quote [^"] - this goes into $1 # If we can't match that, we accept anything up to the next comma instead, & put it into $2 # Lastly, we convert "" to " and print out the cell. 

tieni presente che i file CSV possono contenere celle con nuove righe incorporate all’interno delle virgolette, quindi dovrai eseguire questa operazione se leggi i dati in fila alla volta:

 if("$pre$_"=~/,"[^,]*\z/) { $pre.=$_; next; } $_="$pre$_"; 

Trovare coppie corrispondenti usando espressioni regolari è un’attività non banale e generalmente irrisolvibile. Ci sono molti esempi nel libro delle espressioni regolari di Jeffrey Friedl. Non ce l’ho ora, ma ricordo che ha usato il CSV anche per alcuni esempi.

Puoi (provare a) utilizzare CPAN.pm per fare in modo che il tuo programma installi / aggiorni Testo :: CSV. Come detto prima, puoi persino “installarlo” su una directory home o locale e aggiungere quella directory a @INC (oppure, se preferisci non usare i blocchi BEGIN , puoi use lib 'dir'; – probabilmente è meglio) .

Provato:

 use Test::More tests => 2; use strict; sub splitCommaNotQuote { my ( $line ) = @_; my @fields = (); while ( $line =~ m/((\")([^\"]*)\"|[^,]*)(,|$)/g ) { if ( $2 ) { push( @fields, $3 ); } else { push( @fields, $1 ); } last if ( ! $4 ); } return( @fields ); } is_deeply( +[splitCommaNotQuote('S,"D" REL VALVE ASSY,000771881,')], +['S', '"D" REL VALVE ASSY', '000771881', ''], "Quote in value" ); is_deeply( +[splitCommaNotQuote('S,"BELT V,FAN",000324244,')], +['S', 'BELT V,FAN', '000324244', ''], "Strip quotes from entire value" );