¿Cómo puedo analizar el CSV citado en Perl con una expresión regular?

Tengo algunos problemas con el análisis de datos CSV con comillas. Mi problema principal es con citas dentro de un campo. En el siguiente ejemplo, las líneas 1 – 4 funcionan correctamente, pero 5,6 y 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, 

Me gustaría evitar Text :: CSV, ya que no está instalado en el servidor de destino . Al darme cuenta de que los CSV son más complicados de lo que parecen, estoy usando una receta del Perl Cookbook.

 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. } 

¿Alguien tiene una sugerencia para mejorar la expresión regular para manejar los casos anteriores?

Por favor, intente usar CPAN

No hay ninguna razón por la que no pueda descargar una copia de Text :: CSV o cualquier otra implementación de un analizador CSV que no esté basada en XS e instalarlo en su directorio local o en un directorio lib / sub de su proyecto para que se instale junto con con el lanzamiento de sus proyectos.

Si no puede almacenar archivos de texto en su proyecto, me pregunto cómo está codificando su proyecto.

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

Debería ser una buena guía sobre cómo poner estos en un estado de trabajo a nivel local.

No usar CPAN es realmente una receta para el desastre.

Considere esto antes de intentar escribir su propia implementación de CSV.

Text :: CSV tiene más de un centenar de líneas de código, incluidos errores fijos y casos extremos, y volver a escribir esto desde cero solo hará que descubras cuán espantoso CSV puede ser el camino difícil.

nota: aprendí esto de la manera difícil. Me tomó un día completo obtener un analizador de CSV en PHP antes de descubrir que se había agregado uno incorporado en una versión posterior. Realmente es algo horrible.

Puede analizar CSV utilizando Text :: ParseWords que se envía 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, 

que analiza tu CSV correctamente …

 # => 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: 

El único problema que he tenido con Text :: ParseWords es cuando las comillas anidadas en los datos no se escapan correctamente. Sin embargo, se trata de datos CSV mal construidos y causarían problemas con la mayoría de los analizadores CSV 😉

Entonces puedes notar que

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

salió como (es decir, citas caídas alrededor de “V”)

 # S:YBELT,V:000323030: 

Sin embargo, si escapó como tal

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

entonces las cotizaciones serán retenidas

 # S:YBELT,"V":000323030: 

Esto funciona como el encanto

línea se supone que está separada por comas con embeded,

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

probado; trabajando:-

 $_.=','; # 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. 

Tenga en cuenta que los archivos CSV pueden contener celdas con nuevas líneas incorporadas dentro de las comillas, por lo que deberá hacer esto si lee los datos en línea a la vez:

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

Encontrar pares que coincidan utilizando expresiones regulares es una tarea no trivial y generalmente sin solución. Hay muchos ejemplos en el libro de expresiones regulares Mastering de Jeffrey Friedl. No lo tengo a mano ahora, pero recuerdo que usó CSV para algunos ejemplos también.

Puede (intentar) utilizar CPAN.pm para simplemente hacer que su progtwig instale / actualice Text :: CSV. Como dije antes, incluso puede “instalarlo” en un directorio local o local, y agregar ese directorio a @INC (o, si prefiere no usar bloques BEGIN , puede use lib 'dir'; – probablemente sea mejor) .

Probado:

 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" );