27 de diciembre de 2010

Enviar y recibir datos de un formulario con PHP

Por mucho que nos guste programar en Perl, a veces hay que recurrir a otros lenguajes más adecuados para propósitos concretos. Este es el caso de PHP cuando se trata de realizar interfaces web para nuestros scripts de Perl.
El primer problema que nos surge al pasar a PHP es cómo enviar y recibir datos a través de un formulario web. Los datos enviados los podemos usar para ejecutar en el servidor nuestro script de Perl y generar resultados.
A continuación muestro el ejemplo de un formulario de búsqueda de secuencias DNA o Proteínas:

 <?php  
 // Shows a search input data formulary  
 function showSearchForm(){  
      ?>  
      <h3>Search form:</h3></br>  
      <form enctype="multipart/form-data" action="<?php print $_SERVER['PHP_SELF'] ?>" method="POST">  
      Search name: <input type="text" name="search_name" size="40" maxlength="127"></br></br>  
      Email: <input type="text" name="user_email" size="40" maxlength="127"></br></br>  
      Search input type:  
      <input type="radio" name="search_type" value="stamp"> DNA  
      <input type="radio" name="search_type" value="blastp"> Protein  
      </br></br>  
      Exclude query from results <input type="checkbox" name="search_exclude" value="yes"></br></br>  
      Limit number of results per query: <input type="text" name="search_limit" size="2" maxlength="2"></br></br>  
      Query data or file:</br></br>  
      <textarea name="query_data" rows="10" cols="67"></textarea>  
      </br></br>  
      <input type="file" name="query_file" size="20"></br></br>  
      Genomes:   
      <select name="specie" class="selectbutton">  
      <option value="human">Homo sapiens</option>  
      <option value="mouse">Mus musculus</option>  
      <option value="arabidopsis">Arabidopsis thaliana</option>  
      <option value="rice">Oryza sativa</option>  
      </select>  
      </br></br>  
      <input type="submit" name="search_submitted" value="Search">  
      </form>  
      <?php  
 }  
 ?>  

El siguiente paso consistirá en validar los datos introducidos para volver a mostrar el formulario si los datos fueran erróneos. Usaremos la variable especial $_SESSION para guardar los datos enviados con el formulario (almacenados en la variable $_POST). La variable $_SESSION nos permitirá acceder a su contenido si ejecutamos 'session_start()' al comienzo del código PHP. Antes de guardar los datos enviados, borraremos los contenidos anteriores de $_SESSION. Si en vez de enviar un archivo, hemos enviado datos en formato texto, guardaremos estos datos en un archivo temporal ($_SESSION['query_file']). Si faltan datos en algún campo del formulario, crearemos un mensaje de error en $_SESSION['errors'].

 <?php  
 // Validates search form data  
 function validateSearchData(){  
      // Stores in $_SESSION variable all the post data and in $_SESSION['errors'] if some important data is missing  
      // Clear previous $_SESSION values  
      unset($_SESSION['errors']);  
      unset($_SESSION['search_name']);  
      unset($_SESSION['user_email']);  
      unset($_SESSION['search_type']);  
      unset($_SESSION['search_exclude']);  
      unset($_SESSION['search_limit']);  
      unset($_SESSION['specie']);  
      unset($_SESSION['output_dir']);  
      unset($_SESSION['query_file']);  
      unset($_SESSION['query_data']);  
      (!empty($_POST['search_name'])) ? $_SESSION['search_name'] = trim($_POST['search_name']) : $_SESSION['errors'][] = 'name for the search';  
      (!empty($_POST['user_email'])) ? $_SESSION['user_email'] = trim($_POST['user_email']) : $_SESSION['errors'][] = 'email';;  
      (!empty($_POST['search_type'])) ? $_SESSION['search_type'] = $_POST['search_type'] : $_SESSION['errors'][] = 'search input type';  
      if (!empty($_POST['search_exclude'])){  
           ($_POST['search_exclude'] == 'yes') ? $_SESSION['search_exclude'] = 1 : $_SESSION['search_exclude'] = 0;  
      }  
      (!empty($_POST['search_limit'])) ? $_SESSION['search_limit'] = trim($_POST['search_limit']) : '';  
      (!empty($_POST['specie'])) ? $_SESSION['specie'] = $_POST['specie'] : '';  
      // Store the query data into a file inside the /tmp folder  
      if (!empty($_FILES['query_file']['name']) && !empty($_FILES['query_file']['tmp_name']) && $_FILES['query_file']['size']>0){  
           $_SESSION['query_file'] = $_FILES['query_file']['tmp_name'];  
      }elseif (!empty($_POST['query_data'])){  
           $_SESSION['query_data'] = $_POST['query_data'];  
           $_SESSION['query_file'] = '/tmp/'.$_SESSION['search_name'].".data";  
           $file_handle = fopen($_SESSION['query_file'], "w");  
           fwrite($file_handle, $_POST['query_data']);  
           fclose($file_handle);  
      } else{  
           $_SESSION['errors'][] = 'data or file to process';  
      }  
      if (isset($_SESSION['errors'])){  
           return;  
      }  
 }  
 ?>  

Finalmente, falta introducir las dos funciones anteriores en un archivo (por ejemplo 'index.php') para que sean leídas e interpretadas por el servidor web. Si los datos del formulario son válidos se ejecutará un pequeño código de Perl, si no se volverá a mostrar el formulario indicando los datos que faltan.

 <?php  
 // Example file of a PHP search formulary with input data validation and script execution  
 session_start();  
 ?>  
 <html>  
 <head>  
 <title>Search Form</title>  
 </head>  
 <body>  
 <?php  
 if(!isset($_POST['search_submitted'])){  
      ?><h3>Search form:</h3><?php  
      showSearchForm();  
 } else {  
      validateSearchData();  
      if (!isset($_SESSION['errors'])){  
           executeScript();  
      } else {  
           ?><h3>Search form:</h3><?php  
           print '<font color="red"><b>Please specify: '.join(', ',$_SESSION['errors']).'.</b></font></br></br>';  
           unset($_SESSION['errors']);  
           showSearchForm();  
      }  
 }  
 ?>  
 </body>  
 </html>  
 <?php  
 // Shows an input data formulary filled with previous values  
 function showSearchForm(){  
      ?>  
      <form enctype="multipart/form-data" action="<?php print $_SERVER['PHP_SELF'] ?>" method="POST">  
      Search name: <input type="text" name="search_name" size="40" maxlength="127" <?php ($_SESSION['search_name']) ? print 'value="'.$_SESSION['search_name'].'"' : '' ?> ></br></br>  
      Email: <input type="text" name="user_email" size="40" maxlength="127" <?php ($_SESSION['user_email']) ? print 'value="'.$_SESSION['user_email'].'"' : '' ?> ></br></br>  
      Search input type:  
      <input type="radio" name="search_type" value="stamp" <?php ($_SESSION['search_type'] == 'stamp') ? print 'checked' : '' ?>> DNA  
      <input type="radio" name="search_type" value="blastp" <?php ($_SESSION['search_type'] == 'blastp') ? print 'checked' : '' ?>> Protein  
      </br></br>  
      Exclude query from results <input type="checkbox" name="search_exclude" value="yes" <?php ($_SESSION['search_exclude'] == 'yes') ? print 'checked' : print 'checked' ?>></br></br>  
      Limit number of results per query: <input type="text" name="search_limit" size="2" maxlength="2" <?php ($_SESSION['search_limit']) ? print 'value="'.$_SESSION['search_limit'].'"' : print 'value="10"'; ?> ></br></br>  
      Query data or file:</br></br>  
      <textarea name="query_data" rows="10" cols="67"><?php ($_SESSION['query_data']) ? print $_SESSION['query_data'] : '' ?></textarea>  
      </br></br>  
      <input type="file" name="query_file" size="20"></br></br>  
      Genomes:   
      <select name="specie" class="selectbutton">  
      <option value="human" <?php (!isset($_SESSION['specie']) || $_SESSION['specie'] == 'human') ? print 'selected' : '' ?>>Homo sapiens</option>  
      <option value="mouse" <?php ($_SESSION['specie'] == 'mouse') ? print 'selected' : '' ?>>Mus musculus</option>  
      <option value="arabidopsis" <?php ($_SESSION['specie'] == 'arabidopsis') ? print 'selected' : '' ?>>Arabidopsis thaliana</option>  
      <option value="rice" <?php ($_SESSION['specie'] == 'rice') ? print 'selected' : '' ?>>Oryza sativa</option>  
      </select>  
      </br></br>  
      <input type="submit" name="search_submitted" value="Search">  
      </form>  
      <?php  
 }  
 // Validates search form data  
 function validateSearchData(){  
      // Stores in $_SESSION variable all the post data and in $_SESSION['errors'] if some important data is missing  
      // Clear previous $_SESSION values  
      unset($_SESSION['errors']);  
      unset($_SESSION['search_name']);  
      unset($_SESSION['user_email']);  
      unset($_SESSION['search_type']);  
      unset($_SESSION['search_exclude']);  
      unset($_SESSION['search_limit']);  
      unset($_SESSION['specie']);  
      unset($_SESSION['output_dir']);  
      unset($_SESSION['query_file']);  
      unset($_SESSION['query_data']);  
      (!empty($_POST['search_name'])) ? $_SESSION['search_name'] = trim($_POST['search_name']) : $_SESSION['errors'][] = 'name for the search';  
      (!empty($_POST['user_email'])) ? $_SESSION['user_email'] = trim($_POST['user_email']) : $_SESSION['errors'][] = 'email';;  
      (!empty($_POST['search_type'])) ? $_SESSION['search_type'] = $_POST['search_type'] : $_SESSION['errors'][] = 'search input type';  
      if (!empty($_POST['search_exclude'])){  
           ($_POST['search_exclude'] == 'yes') ? $_SESSION['search_exclude'] = 1 : $_SESSION['search_exclude'] = 0;  
      }  
      (!empty($_POST['search_limit'])) ? $_SESSION['search_limit'] = trim($_POST['search_limit']) : '';  
      (!empty($_POST['specie'])) ? $_SESSION['specie'] = $_POST['specie'] : '';  
      // Store the query data into a file inside the /tmp folder  
      if (!empty($_FILES['query_file']['name']) && !empty($_FILES['query_file']['tmp_name']) && $_FILES['query_file']['size']>0){  
           $_SESSION['query_file'] = $_FILES['query_file']['tmp_name'];  
      }elseif (!empty($_POST['query_data'])){  
           $_SESSION['query_data'] = $_POST['query_data'];  
           $_SESSION['query_file'] = '/tmp/'.$_SESSION['search_name'].".data";  
           $file_handle = fopen($_SESSION['query_file'], "w");  
           fwrite($file_handle, $_POST['query_data']);  
           fclose($file_handle);  
      } else{  
           $_SESSION['errors'][] = 'data or file to process';  
      }  
      if (isset($_SESSION['errors'])){  
           return;  
      }  
 }  
 // Executes a Perl script and prints the output  
 function executeScript(){  
      $output = `perl -e 'print "Hello world\n"'`;  
      print '<pre>'.$output.'</pre>';  
 }  
 ?>  

9 de diciembre de 2010

Compresión de secuencias de ADN

Un problema que me ha surgido recientemente es el de manejar en memoria RAM grandes volúmenes de secuencias de ADN, en mi caso genes, pero que podrían ser también lecturas de secuenciación masiva. Cuando el tamaño total de las secuencias supera la RAM disponible el sistema operativo se verá forzado a hacer swapping/intercambio con el disco y el proceso se ralentiza considerablemente. Una de las posibles soluciones, la que nos ocupa hoy, es la compresión de las secuencias, de manera que ocupen menos espacio físico en RAM.
El siguiente programa muestra cómo podemos realizar esto en Perl, y nos sugiere que esta estrategia vale realmente la pena a medida que el tamaño de las secuencias se aproxima a las 200 bases:




 use strict;  
 use Compress::Zlib;  
   
 # http://perldoc.perl.org/Compress/Zlib.html  
   
 my (@S,$source,$compressed,$uncompressed);  
   
 print "size(source)\tsize(compressed)\tratio\n";  
 for(my $size=25;$size<=500;$size += 25)  
 {  
    # secuencia aleatoria de longitud $size  
    @S = map { ('A','C','G','T')[rand(4)] } 1 .. $size;  
    $source = join(/ /,@S); #print "$source\n";  
   
    $compressed = compress($source);  
    $uncompressed = uncompress($compressed) ;  
      
    printf("%d\t%d\t%g\n",  
       $size,length($compressed),length($compressed)/$size);  
 }  
   

Un saludo,
Bruno

26 de noviembre de 2010

Interfaz SOAP de 3D-footprint

En las recientes Jornadas de Bioinformática 2010 (en Málaga) di una pequeña charla
sobre nuestra base de datos 3D-footprint, con el mismo título del artículo donde la publicamos en NAR "3D-footprint: a database for the structural analysis of protein-DNA complexes" (el  PDF del material presentado está disponible aquí, el vídeo  de la disección de interfaces proteína-DNA aquí).

Logo footprint de 2bam_AB

Tras la presentación me preguntaron si 3D-footprint tenía una interfaz de servicios web. La respuesta es afirmativa, en particular es una interfaz SOAP que se puede usar fácilmente desde Perl, como se muestra en el siguiente ejemplo:

 #!/usr/bin/perl -w  
   
 # prog 10 Bruno Contreras-Moreira  
 # web services client for 3D-footprint  
 # ejemplo basado en http://floresta.eead.csic.es/3dfootprint/tutorial.html#ws  
   
 use strict;  
 use SOAP::Lite;  
   
 my $URI  = 'http://floresta.eead.csic.es/footprint';  
 my $WSURL = 'http://floresta.eead.csic.es/3dpwm/scripts/server/ws.cgi';  
   
 my $soap = SOAP::Lite  
 -> uri($URI)  
 -> proxy($WSURL);  
   
 # 1) consulta de secuencia proteica (de un posible factor de transcripción)  
 my $secuencia =   
 "IYNLSRRFAQRGFSPREFRLTMTRGDIGNYLGLTVETISRLLGRFQKSGMLAVKGKYITIENNDALAQLAGHTRNVA";  
   
 my $result = $soap->protein_query($secuencia); # 1 param: 1(string) sequence  
   
 unless($result->fault){ print $result->result(); }   
 else{ print 'error: ' . join(', ',$result->faultcode,$result->faultstring); }  
   
 # los resultados muestran los posibles residuos de interfaz proteína-DNA  
 # en mayúsculas  
   
   
 # 2) consulta de motivo de DNA  
 my $motivo = 'tntga';  
 $result = $soap->motif_query($motivo,'local',1);   
 # 3 params: 1(string) motif in IUPAC|CONSENSUS|TRANSFAC format,   
 # 2(string) local|global,   
 # 3(float) evalue cutoff  
   
 unless($result->fault){ print $result->result(); }   
 else{ print 'error: ' . join(', ',$result->faultcode,$result->faultstring); }  
   
 # los resultados muestran complejos proteína-DNA cuyos motivos de DNA reconocidos  
 # se parecen a éste  

19 de noviembre de 2010

Escapar caracteres especiales en un texto antes de utilizar expresiones regulares

Hoy me he encontrado con un pequeño bug del script 'pfam_scan.pl' que sirve para buscar diferentes secuencias de proteínas en una base de datos de dominos de  Pfam (HMMs), todo ello usando la herramienta 'hmmscan' de HMMER3.

El error simplemente consistía en que una subrutina insertaba variables dentro de una expresión regular sin haber escapado los caracteres especiales previamente, con lo cual se insertaban directamente caracteres del tipo '(', '/', ... directamente dentro de la expresión regular y no funcionaba como era deseado.

Para evitarlo basta con escapar dichos caracteres especiales insertando el texto entre '\Q' y '\E' como bien nos ha sugerido Joaquín (más información en perlop):

 my $text_with_special_chars = 'abc$%()/def';  
 my $start_of_text_to_search = '$%';  
   
 $text_with_special_chars =~ /(\Q$start_of_text_to_search\E.+)/;  
   
 print $1; # $1 = '$%()/def'  

15 de noviembre de 2010

Mezclando datos en un solo archivo Excel

A menudo me ha tocado intercambiar datos con colegas en formato Excel y uno de los problemas que he padecido es el de tener que ir juntando a mano diferentes conjuntos de datos en diferentes hojas del mismo libro de cálculo. Una molestia añadida es la necesidad de importar archivos en formato texto, como columnas separadas por tabuladores, por ejemplo, en el mismo libro. Para automatizar este tipo de tareas y usar Excel/OpenOffice realmente sólo para el análisis de los datos, podemos echar mano por ejemplo del módulo Spreadsheet del CPAN. El mismo módulo puede servir además para leer y procesar datos directamente desde archivos Excel.

Volviendo al problema descrito arriba, supongamos por ejemplo que queremos unir en un sólo archivo excel tres archivos (datos1.xls, datos2.xls y datos3.tab) en uno solo que llamaremos todos_datos.xls . Pues bien, por medio del programa descrito debajo sería tan sencillo como ejecutar en el terminal:

 $ ./my_xls_merge.pl todos_datos.xls datos1.xls datos2.xls datos3.tab

Yo lo he probado en Linux y los archivos generados se leen perfectamente con el MS Excel, con la única pega de que se pierden las gráficas que hubiera ya en los archivos de entrada.

El código es el siguiente:

 #!/usr/bin/perl -w  
   
 # programa 9 my_xls_merge.pl   
 # programita para unir hojas de diferentes archivos .xls o .tab en un solo libro excel
 # OJO: conserva el valor de cada celda de una hoja de calculo, no las formulas,   
 # y se pierden los diagramas por ejemplo  
 # inspirado por http://www.perlmonks.org/?node_id=743574  
   
 use strict;  
 use Spreadsheet::ParseExcel;  
 use Spreadsheet::WriteExcel;   
 use File::Basename;  
   
 my $MAXLENGTHSHEETNAME = 31;  
   
 die "# usage: $0 <outfile.xls> <file1.xls> ... <fileN.xls>\n" if(scalar(@ARGV) < 2);  
   
 my @infiles = @ARGV;  
 my $outfile = shift(@infiles);  
   
 if(-e $outfile)  
 {  
    print "# a file named '$outfile' already exists, overwrite? (Y/N)\n";  
    my $overwrite = <STDIN>;  
    if($overwrite ne "Y\n"){ exit }  
    else{ unlink($outfile) }  
 }  
   
 # instantiate resulting excel object  
 my $outbook = Spreadsheet::WriteExcel->new($outfile) ||   
    die "# $0: cannot create '$outfile': $!";  
   
 for my $file (@infiles)   
 {  
    if(!-e $file)  
    {  
       print "# skipping non-existing file '$file'\n";  
       next;  
    }  
   
    # remove path   
    my ($filename,$copysheet_name) = (fileparse($file));   
      
    my $excel = Spreadsheet::ParseExcel::Workbook->Parse($file);  
    if(defined($excel->{'File'})) # excel format infiles  
    {  
       foreach my $sheet (@{$excel->{Worksheet}})   
       {   
          $copysheet_name = $sheet->{'Name'} . '-' . $filename;  
          if(length($copysheet_name) > $MAXLENGTHSHEETNAME)  
          {  
             print "# abbreviating $copysheet_name to ";  
             $copysheet_name = substr($copysheet_name,0,$MAXLENGTHSHEETNAME-3).'...';  
             print "$copysheet_name\n";  
          }  
         print "# adding '$file' sheet '$sheet->{'Name'}' ($copysheet_name)\n";  
   
        my $copysheet = $outbook->add_worksheet($copysheet_name);  
            
          $sheet->{'MaxRow'} ||= $sheet->{'MinRow'};  
          foreach my $row ($sheet->{'MinRow'} .. $sheet->{'MaxRow'})   
          {  
               my @rowdata = map { $sheet->{'Cells'}->[$row]->[$_]->{'Val'} }   
                      $sheet->{'MinCol'} .. $sheet->{'MaxCol'};  
          $copysheet->write($row,0,\@rowdata);   
          }  
       }  
    }  
    elsif($file =~ /\.tab/)# .tab infiles, seria trivial manejar .csv por ejemplo  
    {  
       $copysheet_name = $filename;  
       print "# adding '$file' no_sheet ($copysheet_name)\n";  
         
       my $copysheet = $outbook->add_worksheet($copysheet_name);  
         
       open(INTAB,$file) || die "# $0 : cannot read '$file': $!";  
       while(<INTAB>)  
       {  
          chomp($_);  
          my @tabdata = split(/\t/,$_);  
          $copysheet->write($.-1,0,\@tabdata);  
       }  
       close(INTAB);  
    }  
 }  
   
 $outbook->close();  
 print "# outfile: $outfile\n";   
   

Un saludo