Análisis de cadenas con datos separados por comas

Supongamos que tenemos cierto texto en $text proveniente de un fichero CSV (Comma Separated Values). Esto es el fichero contiene líneas con el formato:

"earth",1,"moon",9.374

Esta línea representa cinco campos. Es razonable querer guardar esta información en un array, digamos @field, de manera que $field[0] == 'earth', $field[1] == '1', etc. Esto no sólo implica descomponer la cadena en campos sino también quitar las comillas de los campos entrecomillados. La primera solución que se nos ocurre es hacer uso de la función split:

@fields = split(/,/,$text);

Pero esta solución deja las comillas dobles en los campos entrecomillados. Peor aún, los campos entrecomillados pueden contener comas, en cuyo caso la división proporcionada por split sería errónea.

   1 #!/usr/bin/perl -w
   2 use Text::ParseWords;
   3 
   4 sub parse_csv {
   5   my $text = shift;
   6   my @fields = (); # initialize @fields to be empty
   7 
   8   while ($text =~ 
   9     m/"(([^"\\]|\\.)*)",? # quoted fields
  10       | 
  11       ([^,]+),?           # $3 = non quoted fields
  12       | 
  13       ,                   # allows empty fields
  14     /gx 
  15     )
  16   {
  17     push(@fields, defined($1)? $1:$3); # add the just matched field
  18   }
  19   push(@fields, undef) if $text =~ m/,$/; #account for an empty last field
  20   return @fields;
  21 }
  22        
  23 $test = '"earth",1,"a1, a2","moon",9.374';
  24 print "string = \'$test\'\n";
  25 print "Using parse_csv\n:";
  26 @fields = parse_csv($test);
  27 foreach $i (@fields) {
  28   print "$i\n";
  29 }
  30 
  31 print "Using Text::ParseWords\n:";
  32 #  @words = &quotewords($delim, $keep, @lines);  
  33 #The $keep argument is a boolean flag.  If true, then the
  34 #tokens are split on the specified delimiter, but all other
  35 #characters (quotes, backslashes, etc.) are kept in the
  36 #tokens.  If $keep is false then the &*quotewords()
  37 #functions remove all quotes and backslashes that are not
  38 #themselves backslash-escaped or inside of single quotes
  39 #(i.e., &quotewords() tries to interpret these characters
  40 #just like the Bourne shell). 
  41 
  42 @fields = quotewords(',',0,$test);
  43 foreach $i (@fields) {
  44   print "$i\n";
  45 }

Las subrutinas en Perl reciben sus argumentos en el array @_. Si la lista de argumentos contiene listas, estas son ``aplanadas'' en una única lista. Si, como es el caso, la subrutina ha sido declarada antes de la llamada, los argumentos pueden escribirse sin paréntesis que les rodeen:

@fields = parse_csv $test;

Otro modo de llamar una subrutina es usando el prefijo &, pero sin proporcionar lista de argumentos.

@fields = &parse_csv;
En este caso se le pasa a la rutina el valor actual del array @_.

Los operadores push (usado en la línea 17) y pop trabajan sobre el final del array. De manera análoga los operadores shift y unshift lo hacen sobre el comienzo. El operador ternario ? trabaja de manera análoga como lo hace en C.

El código del push podría sustituirse por este otro:

push(@fields, $+);
Puesto que la variable $+ contiene la cadena que ha casado con el último paréntesis que haya casado en el ultimo ``matching''.

La segunda parte del código muestra que existe un módulo en Perl, el módulo Text::Parsewords que proporciona la rutina quotewords que hace la misma función que nuestra subrutina.

Sigue un ejemplo de ejecución:

> csv.pl
string = '"earth",1,"a1, a2","moon",9.374'
Using parse_csv
:earth
1
a1, a2
moon
9.374
Using Text::ParseWords
:earth
1
a1, a2
moon
9.374

Casiano Rodríguez León
Licencia de Creative Commons
Principios de Programación Imperativa, Funcional y Orientada a Objetos Una Introducción en Perl/Una Introducción a Perl
por Casiano Rodríguez León is licensed under a Creative Commons Reconocimiento 3.0 Unported License.

Permissions beyond the scope of this license may be available at http://campusvirtual.ull.es/ocw/course/view.php?id=43.
2012-06-19