Actualizacion automática de tablas en documentos de Latex (II)

Enviado por pvaldes el 23 Noviembre, 2011 - 18:03.

Casos especiales

En la primera parte habíamos visto como crear una tabla en código de latex desde una base de Postgresql y mantenerla permanentemente actualizada de un modo relativamente sencillo. Hoy vamos a usar Perl para enfocar el mismo problema desde un punto de vista mucho más interesante. Normalmente evito hacer las cosas de modo complicado cuando ya tengo una solución simple que sé que funciona, así que podéis confiar en que habrá algún caramelito esperándonos al final.

Empezamos, para hacer el trabajo he escrito el siguiente programa, que podéis copiar y guardar si lo deseáis:

#!/usr/bin/perl -w
use strict; 
use DBI;
use Getopt::Long;
use Pod::Usage;

  chomp (my $user = `whoami`);
  my $help = 0;   GetOptions('help|h|?' => \$help) || pod2usage(-verbose => 1);

BUCLE_PRINCIPAL:

if (scalar @ARGV == 0){pod2usage(-verbose => 0, -message => "$0: No se indica un nombre de base de datos a la que conectarse")}

elsif (scalar @ARGV == 2 ){
  my ($mibase, $mes) = @ARGV;
  my $dbh2 = DBI->connect("DBI:Pg:dbname=$mibase; port = '5432'", $user, '', {RaiseError => 1, AutoCommit => 1})
               or die "conexión fallida: ", DBI->errstr;
  #
  ENCABEZADO:   
  open my $salida, '>', "tabla.tex";
   my $it = '\\textit';
   print $salida "\\documentclass\{article\}\n\\usepackage\[usenames,dvipsnames\]\{color\}\n\\usepackage\{booktabs\}\n\\begin\{document\}\n";
     print $salida "\\begin\{table\}\\centering\\begin\{tabular\}\[h\]\{","r" x 6,"\}\\toprule\n";
     print $salida "$it\{mes\} & $it\{temp\} & $it\{hora\} & $it\{lu\} & $it\{serie\} & $it\{smin\}\\\\ \\midrule\n";
  #
  TABLA_DINAMICA: 
    my $sth2 = $dbh2->prepare("SELECT * from datos where mes = ? order by temp desc"); $sth2->execute($mes);
    while (my @col = $sth2->fetchrow_array)  {
      print $salida $col[5], " & ", $col[0]," & ";
      printf $salida ("%.2f & ", $col[1]);
      printf $salida ("%.1f & ", $col[2]);
      print $salida $col[4];
        if ($col[3] > 2 && $col[3] < 6){print $salida " & \\textcolor\{black\}\{", $col[3], "\} \\\\\n";}
        elsif ($col[3] >= 6){print $salida " & \\textcolor\{red\}\{\\bf\{", $col[3], "\}\} \\\\\n";}
        else {print $salida " & \\textcolor\{Gray\}\{", $col[3], "\} \\\\\n";}
     }
  PIE_DE_TABLA:
     print $salida "\\bottomrule\n\\end\{tabular\}\\caption\{Resultados para $mes\}\\end\{table\}\n\\end\{document\}\n";
  close $salida;
#
  $dbh2->disconnect();
  `pdflatex tabla.tex`;  `xpdf tabla.pdf`;
}

else {  pod2usage(-verbose => 0) if ($help) }
__END__

=head1 NAME

Crear tablas dinámicas de LaTeX desde información contenida en una base de datos Postgres

=head1 SYNOPSIS

perl informe DATABASE mes

perl informe OPCIONES

Programa para conectarse a una base de datos B<PostgreSQL> y devolver un pdf con los resultados de una consulta sobre un mes concreto

=head2 OPCIONES

--help -h -?

=head1 TODO

Soporte para multirow

=head1 COPYRIGHT

Copyright (c) 2011 pvaldes.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. See perldoc perlartistic and perldoc perlgpl for the details.

=cut

Explicación del programa:

  • La línea 1 indica que el programa consiste en código de perl y la ruta al binario que debe ejecutarlo
  • La línea 2 indica que programaremos de modo estricto (util contra fallos)
  • Las líneas 3 a 5 cargan diversos módulos que vamos a necesitar, DBI para "hablar" con la base de datos y Getopt y Pod que servirán opcionalmente para documentar el programa
  • Línea 8. Captura el nombre de vuestro usuario para conectaros a vuestra base, sea cual sea
  • Línea 9. Declara las opciones extra para la línea de comandos
  • Línea 13. Lanza la ayuda si no se proporcionan argumentos al programa
  • Línea 15. Comprueba que el programa se ha lanzado con dos argumentos. Se requieren el nombre de la base de datos y el nombre para la consulta
  • Línea 16. Captura los argumentos proporcionados y asigna su valor a dos variables
  • Líneas 17-18. Se conecta a la base de datos y crea un objeto que va a representarla, si no podemos realizar una conexión exitosa (por ejemplo porque no exista una base de datos con el nombre indicado) muestra un mensaje de error
  • ENCABEZADO: crea un archivo e imprime un preámbulo y comandos para que pueda compilares posteriormente con latex. se cargan los paquetes color y booktabs de latex que necesitaremos posteriormente. El encabezado tiene que ajustarse a mano, pero tendremos que realizar esta tarea solamente una vez. Es importante que en número de columnas sea igual (o menor) que el devuelto por la consulta, nunca superior.
  • TABLA_DINAMICA: hace una consulta a la base de datos, e imprime los datos al archivo. Esta parte cambiará cada vez que actualicemos la tabla.
  • PIE_DE_TABLA: incluye comandos para cerrar la tabla y el archivo y añade un pie de tabla dinámico
  • Línea 39. Cierra el archivo
  • Línea 41. Nos desconecta de la tabla correctamente
  • Linea 42. Si tenemos instalado texlive esta línea compilará el resultado a un pdf y lo mostrará.
  • Línea 45. Crea una excepción para indicar al programa que muestre la documentación cuando se lance sin argumentos pero con la opción --help
  • Todo lo que aparece tras el __END__ es documentación del programa para ser extraida por pod2usage. También podemos verla con
    perldoc nombre_de_programa. La razón de incluir manuales y un copyright a las cosas que pongo en internet obedecen simplemente a procurar obligarme a mí mismo a seguir unas buenas prácticas de programación, no tiene mayor importancia que esa.

Este sistema posee dos grandes ventajas frente al visto en la primera parte. De entrada podemos calcular la tabla en base a parámetros que no son fijos, si llamamos a nuestro programa INFORME por ejemplo ahora podríamos lanzar el programa así

$ INFORME campaña_2008 septiembre
$ INFORME campaña_2011 septiembre
$ INFORME campaña_2011 octubre

Y en cada caso obtendríamos diferentes tablas con los valores deseados.

Además nos abre las puertas a un control mucho más fino sobre el aspecto final de la tabla.
Examinemos por ejemplo la siguiente tabla, obtenida mediante una versión ligeramente modificada de nuestro programa en que el argumento introducido esta vez es el nombre científico de un animal, los datos son inventados y son lo de menos en este caso:

Examinando el código y la imágen de arriba podemos ver por ejemplo como el pie de figura ha sido convenientemente rellenado por el programa con el nombre correcto y lo ha formateado en cursiva (requerido por ser un nombre científico), también podemos notar que el valor de los decimales en la segunda y tercera columnas ha sido redondeado, y en la columna final ocurre además algo más interesante: el programa ha evaluado cada valor de esa columna por separado y lo ha formateado en consecuencia. Cuando el valor de la última columna sobrepase cierta cantidad, aparecerá resaltado en rojo y negrita, si es un valor muy bajo aparecerá en gris claro y si no cumple ninguna de esas condiciones se dejará como texto normal. Nuestra tabla de ejemplo es muy sencilla, pero podemos figurarnos lo práctico que podría ser tener una tabla enorme con muchas columnas en la cual los valores "interesantes" estuvieran resaltados automáticamente por el programa para facilitarnos su detección de un sólo vistazo.

Usar Perl para actuar de intermediario entre Latex y Postgresql proporciona por tanto una gran versatilidad y nos garantiza un gran margen de maniobra para controlar el resultado final. Podremos cambiar los valores muy altos o muy bajos por ``-outlier-'' o ``error'', o bien normalizar la ortografía para un determinado valor que hemos escrito de varias maneras distintas, o mostrar gráficamente los valores fríos y calientes de una variable física en una matriz, o borrar los valores inferiores a cierta cantidad (pintándolos de blanco), o añadir gráficas o cualquier otra cosa que nos apetezca.

No quiero acabar sin observar que usar argumentos implica también una pequeña contrapartida, este tipo de programas no están pensados para usarse desde CRON, pero aún podremos refrescar las tablas a mano lanzando el programa siempre que lo deseemos y de todos modos es un sacrificio menor a cambio de las ventajas de acceder a distintos datos con el mismo programa a demanda. Nada nos impide tampoco modificarlo para fijar un valor por defecto para los argumentos y dejarlo entonces en CRON, claro.