jueves, 29 de enero de 2015

Pandoc: Plantillas personalizadas de LaTeX para generación de documentos pdf

[Esta entrada es una traducción libre al castellano de la versión original escrita en inglés en este mismo blog.]

Una de las características más interesantes, aunque pocas veces comentada, de Pandoc es la de sus plantillas para LaTeX. Las plantillas de Pandoc nos permiten seguir escribiendo en Markdown, en lugar de volver de nuevo a LaTeX puro, incluso en los casos en que tengamos que producir documentos que no se considerarían "normales". En tales situaciones es posible también aprovecharse de la simplicidad de Markdown y relegar todos los detalles engorrosos a nuestras plantillas, siempre y cuando nuestras plantillas se atengan a la sintaxis y semántica de Pandoc.

Como ejemplo ilustrativo, supongamos que debemos entregar con regularidad actas del día de reuniones en pdf, siguiendo un diseño formal, especializado y con mucho texto repetido de un acta a otra como el que se muestra en la siguiente imagen:

Pandoc no parece ser la mejor herramienta para esta tarea. Trataré de demostrar que, por el contrario, Pandoc es especialmente apropiado para ella.

Plantillas de Pandoc

Como todo usuario de Pandoc sabe, la conversión de Markdown a LaTeX en Pandoc se basa en una plantilla por defecto que viene incluida en la instalación de Pandoc. Si se ejecuta esta orden:
pandoc --standalone --output mi_documento.pdf mi_documento.md

O, con las formas abreviadas de las opciones:

pandoc -s -o mi_documento.pdf mi_documento.md

se está aplicando un plantilla LaTeX por defecto, llamada default.latex, que en mi sistema y versión actual de Pandoc (1.12.2.1) se encuentra en $PANDOC_DIR/data/templates/default.latex. El resultado de aplicar esa plantilla al documento Markdown es un fichero que la máquina LaTeX (actualmente, pdflatex es la máquina por defecto) procesa de forma transparente para producir el pdf final.

Un rápido vistazo a la plantilla por defecto muestra que dicha plantilla no es más que un fichero LaTeX normal y corriente en el que, además, se incluyen, ciertos determinados constructos (variables, condicionales y bucles).

Si queremos usar otra plantilla, en lugar de la plantilla por defecto, ya sea una versión personalizada de dicha plantilla, ya una plantilla que creemos nosotros mismos completamente desde cero, necesitaremos pasar el nombre del fichero que la contiene a la opción --template. Suponiendo que el fichero de entrada y nuestra plantilla están en el mismo directorio, la orden correspondiente sería algo parecido a esto:

pandoc -s --template="mi_plantilla.latex" -o mi_documento.pdf mi_documento.md

Variables en Pandoc

Una variable en Pandoc tiene la siguiente sintaxis:
$nombre-de-variable$

Cuando ejecutamos pandoc cada variable de la plantilla se substituye por su valor. Este valor se puede pasar a Pandoc de distintas formas. Una de ellas, como veremos más adelante, es pasárselo a través de la opción de línea de órdenes -M nombre-de-variable=valor(donde -M es la forma abreviada de --metadata).

Por lo respecta a las variables pre-definidas por Pandoc y que están incluidas en la plantilla por defecto, más información sobre la mayoría de ellas se encuentra en la documentación oficial (http://johnmacfarlane.net/pandoc/README.html#templates).

Para saber en concreto qué variables hay en la plantilla por defecto podemos también utilizar un filtro Unix como el siguiente:

grep -o '\$.*\$' /usr/share/pandoc/data/templates/default.latex \
| grep -v '\$endif\$\|\$else\$\|\$endfor\$'

Es especialmente importante destacar que hay una variable crítica pre-definida, la variable $body$, que toda plantilla debería incluir, puesto que el contenido propiamente tal de nuestro documento de entrada será introducido en su lugar.

Comprobemos esto último. Creemos una platilla LaTeX simple.latex con este contenido:

\documentclass{minimal}
\begin{document}
$body$
\end{document}

y ejecutemos pandoc para que reciba el input de la entrada estándar desde terminal. El resultado de nuestra sesión de prueba es el siguiente:

$ pandoc -s --template="simple.latex" --to latex
Hola
Ctrl+D
\documentclass{minimal}
\begin{document}
Hola
\end{document}

La primera línea es el comando ejecutado. Estoy pidiendo a Pandoc que aplique nuestra plantilla, simple.latex a la entrada que le vamos a pasar y que la convierta a formato LaTeX. Las dos siguientes líneas reproducen lo que he tecleado para que Pandoc lo consuma. Ctrl+D señala EOF (fin de fichero) y cierra la entrada estándar. El resto es la salida que Pandoc produce. Nótese que lo que he tecleado, "Hola", aparece tras el procesamiento, como esperábamos, en el lugar en que estaba $body$ en la plantilla.

Intentemos algo un poco más complicado. Añadamos una variable de nuestra cosecha, que llamaremos $saludo$, a la plantilla:

\documentclass{minimal}
\begin{document}
$saludo$
$body$
\end{document}
y comprobemos qué pasa:
$ pandoc -s -M saludo="Hola gente" --template="simple.latex" --to latex
Esto es Pandoc
Ctrl+D
\documentclass{minimal}
\begin{document}
Hola gente
Esto es Pandoc
\end{document}

La orden es casi idéntica a la de antes. El añadido clave es la opción -M comentada previamente. A diferencia de la variable predefinida $body$, tenemos ahora que pasar los valores de nuestras propias variables a pandoc a través de la opción -M. Como vemos, en la salida, la variable en la plantilla es sustituida por el que valor que hemos dado.

Condicionales

Los condicionales tienen esta sintaxis:
$if(variable)$
X
$else$
Y
$endif$ 

donde la cláusula $else$ es opcional.

Supongamos que nos interesa poder elegir, cuando sea necesario, entre diferentes clases de documentos para el mismo documento de entrada. En concreto, supongamos que queremos crear un documento minimal por defecto, a no ser que pidamos expresamente que el documento sea de otra determinada clase. Podemos conseguirlo a través de este condicional:

$if(mi-clase-doc)$
$mi-clase-doc$
$else$
minimal
$endif$

Hay que tener cuidado con la sintaxis. Cada cláusula (if(), else, endif) va rodeada por el signo $. Las variables que serán remplazadas por sus valores también van entre $. Los valores literales, así como las referencias a la variable en la cláusula if van sin ese signo.

Naturalmente, nuestro condicional debe colocarse en el lugar adecuado en la plantilla, a saber, la instrucción \documentclass:

\documentclass{$if(mi-clase-doc)$
               $mi-clase-doc$
               $else$
               minimal
               $endif$}
Escribir lo anterior en una sola línea quizá sea menos legible, pero también más característico del estilo LaTeX. Pongámosla, pues, así:
\documentclass{$if(mi-clase-doc)$$mi-clase-doc$$else$minimal$endif$}
\begin{document}
$saludo$
$body$
\end{document}

Toca poner a prueba la plantilla:

$ pandoc -s -M saludo="Hola gente" --template="simple.latex" --to latex
Esto debería ser minimal
Ctrl+D
\documentclass{minimal}
\begin{document}
Hola gente
Esto debería ser minimal
\end{document}

¡Funciona!

Probemos ahora la otra posibilidad:

$ pandoc -s -M saludo="Hola gente" -M mi-clase-doc="book" --template="simple.latex" --to latex
Y esto, book
Ctrl+D
\documentclass{book}
\begin{document}
Hola gente
Y esto, book
\end{document}

¡También funciona! Nótese que en esta ocasión hemos establecido el valor de la variable mi-clase-doc a "book" a través de la opción -M, tal como hicimos anteriormente con $saludo$.

Bucles

Los bucles funcionan de una manera semejante. La sintaxis básica de un bucle es la siguiente:
$for(variable)$
X
$sep$separador
$endfor$

La línea $sep$separador es opcional. Sirve para definir un separador entre elementos consecutivos.

Digamos que queremos anotar los participantes a una reunión en la primera línea de nuestro documento. Podemos definir una variable $partipante$ en nuestra plantilla y dejar que Pandoc rellene su contenido. Queremos además que los nombres de los participantes aparezcan separados por una coma. Podríamos hacer todo esto añadiendo lo siguiente a nuestra plantilla:

$for(participante)$
$participante$
$sep$, 
$endfor$

O en una sola línea y en el lugar de la plantilla que corresponde:

\documentclass{$if(mi-clase-doc$$mi-clase-doc$$else$minimal$endif$}

\begin{document}
Participantes: $for(participante)$$participante$$sep$, $endfor$

$saludo$
$body$
\end{document}

Hagamos de nuevo una prueba. Ahora añadiremos a la orden pandoc tantos -M participante=... como participantes queremos incluir.

$ pandoc -s -M saludo="Hola gente" -M participante="W. Shakespeare" -M participante="Edgar A. Poe" --template="simple.latex" --to latex
Menudo plantel
Ctrl+D
\documentclass{minimal}
\begin{document}
Participantes: W. Shakespeare, Edgar A. Poe

Hola gente
Menudo plantel
\end{document}

¡Estupendo! Todo funciona perfecto.

Bloques de meta-datos

Es, como poco, engorroso tener que pasar todas estas cosas a la línea de órdenes. No es obligatorio, por supuesto. Pandoc proporciona para esta tarea los así llamados bloques de meta-datos. Un bloque de meta-datos para nuestro experimento anterior tendría este aspecto:
---
mi-clase-doc: minimal
saludo: Hola gente
participante:
- William Shakespeare
- Edgar A. Poe
---

Este bloque no es más que un fragmento de texto que sigue las especificación YAML (http://yaml.org/spec). Aparte de esto, los bloques YAML que se incluyen en un documento para que Pandoc lo procese deben empezar con una línea de tres guiones y terminar con una línea de tres puntos o tres guiones como se ve en el ejemplo.

Un bloque de meta-datos consta de campos. Cada campo tiene un nombre y un valor asociado a ese nombre, separado del nombre por dos puntos. Algunos campos pueden contener varios valores, los cuales van precedidos por un guion, tal y como aparecen para el campo 'participante' en el ejemplo.

Estos bloques nos permiten pasar a pandoc toda la información requerida sin tener que complicarnos la vida con las opciones en la línea de órdenes. La forma habitual de usar estos bloques es añadirlos al principio de nuestro documento de entrada. Otra forma, en mi opinión mejor, es crear un fichero yaml que pasamos a la vez que el fichero de entrada.

Por ejemplo, si guardamos la entrada de nuestro último experimento (la cadena "Menudo plantel") en un fichero con nombre mi_documento.md y el bloque de meta-datos en un fichero con nombre variables.yaml, podemos llamar a pandoc como sigue para conseguir exactamente la misma salida que obtuvimos antes:

pandoc -s --template="simple.latex" --to latex mi_documento.md variables.yaml

El documento final de la reunión

Nuestra tarea inicial se vuelve realizable gracias a la flexibilidad de Pandoc, que acabamos de explicar. Es cuestión sencillamente de crear un documento LaTeX normal y corriente (nuestra plantilla) con unas pocas variables y un bucle. Todos los detalles complicados y el texto repetitivo se desplaza a la plantilla, mientras que el contenido relevante y propiamente tal puede escribirse como de costumbre y muy convenientemente en Markdown puro.

Como referencia reproduzco todos los ficheros implicados en la creación del documento que mostré al principio de esta entrada. No voy a añadir nada más acerca del LaTeX que he usado, que es, dicho sea de paso, un poco chapucero ;-) En cualquier caso, asumo que los lectores conocen LaTeX lo suficiente.

mi_documento.md

1.  Lorem ipsum dolor sit amet,

    Lorem ipsum dolor sit amet consectetur adipiscing elit,
    sed do eiusmod tempor incididunt ut labore et dolore
    magna aliqua.

2.  Ut enim ad minim veniam,

    Ut enim ad minim veniam, quis nostrud exercitation ullamco
    laboris nisi ut aliquip ex ea commodo consequat.

3.  Duis aute irure dolor

    Duis aute irure dolor in reprehenderit in voluptate velit
    esse cillum dolore eu fugiat nulla pariatur.

4.  Excepteur sint occaecat

    Excepteur sint occaecat cupidatat non proident, sunt in
    culpa qui officia deserunt mollit anim id est laborum

mi_plantilla.latex

\documentclass[a4paper]{extreport}
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage[spanish]{babel}
\usepackage{marginnote}
\usepackage{background}

\setlength{\parindent}{0pt}

\SetBgScale{1}
\SetBgColor{black}
\SetBgAngle{0}
\SetBgHshift{-0.52\textwidth}
\SetBgVshift{-1mm}
\SetBgContents{\rule{0.4pt}{\textheight}}

\setlength{\marginparwidth}{32mm}
\renewcommand*{\raggedleftmarginnote}{}
\reversemarginpar

\begin{document}
\textbf{Informe}\marginnote{$for(asistente)$$asistente$$sep$\\ $endfor$}

$body$

\section{}
Freedonia, $dia$ de $mes$ de $ano$ 

\section{}
Jefe del Departamento

\vspace{2cm}
\emph{Firma: átopos}
\end{document}

variables.yaml

---
dia: 25
mes: Enero
ano: 2015
asistente:
- William Shakespeare
- Edgar A. Poe
- Miguel de Cervantes
---

La orden de Pandoc que genera el pdf

pandoc -s --template="mi_plantilla.latex" -o mi_documento.pdf mi_documento.md variables.yaml

2 comentarios:

  1. Muchísimas gracias. Llevo días buscando esta información condensada de esta forma. Felicidades, es magnífico! un saludo.

    ResponderEliminar