Crear un índice web en Org Mode a partir de los marcadores de hyperref en LATEX
La situación real es la siguiente. He comenzado hace unos días la composición tipográfica del Diccionario Hispánico de
la Tradición Clásica y, en cada entrada de este diccionario, voy a añadir un marcador personalizado de hyperref.sty
.
Todos esos marcadores serán recolectados en el futuro a fin de montar con ellos una página web que contenga un índice de
entradas. Cada enlace de esa web dirigiría, por tanto, a una entrada concreta del PDF. Todo esto, claro, porque está
previsto que el volumen cuente también con una versión on line, además de la versión impresa. Y la versión on line
estará contenida en una página web con contenido diverso relacionado con el diccionario. Mi intención es realizar ese
índice web mediante Org Mode. Y automatizar todo el proceso gracias a una serie de funciones en Elisp que escribí a tal
efecto. Explicare aquí un poco los intríngulis del proceso1.
Antes de nada, defino en el preámbulo de mi documento .tex
destinado al trabajo con el diccionario un sencillo comando
para los marcadores de hyperref
. Pero como este paquete no lo voy a tener activo siempre (para la versión impresa no
me hace falta), el comando a definir en cuestión tendrá dos versiones: una «con pulpa», para cuando hyperref
esté
cargado. Y otra que no haga nada ni sepa nada de hyperref
. Así me ahorro de tener que andar comentando y descomentando
los marcadores, según sea el PDF en versión on line o impresa. Mi macro para los marcadores, pues, la defino mediante
un simple condicional:
% \usepackage{hyperref} \newif\iflabeldict \labeldictfalse \iflabeldict \def\labeldict#1{\hypertarget{#1}{}} \else \def\labeldict#1{} \fi
Es decir, cuando el valor sea \labeldicttrue
(y no ...false
), mi macro \labeldict{...}
insertará un marcador al
PDF en el lugar donde esté. En su argumento introduciré los títulos de cada entrada, pero (y es importante esto) con
guiones en lugar de espacios. Así, una entrada titulada «La entrada que sea» me dará el marcador de PDF
#La-entrada-que-sea
. Una vez terminado con la parte que atañe a LATEX, pasamos a la parte que le toca a Emacs
Lisp, que es el que hará todo el trabajo de peso.
Mi función en Elisp
Empezamos por definir una variable para la lista de entradas. De momento le damos el valor nil
:
(setq entradas-diccionario nil)
A continuación, esta pequeña función que me será útil para copiar el argumento de cada \labeldict{...}
(para situarme
sin ambigüedad posible entre las llaves latexianas, echo mano de las funciones del paquete smartparens
, que debe estar
instalado y convenientemente requerido (la función asume, por otra parte, que estamos dentro de un par de llaves. Lo
único que hace es marcar una región entre el principio y el fin de las llaves y añadirla a nuestro kill-ring
):
(defun copia-argumento () (interactive) (sp-end-of-sexp) (set-mark-command nil) (sp-beginning-of-sexp) (region-active-p) (copy-region-as-kill (region-beginning) (region-end)) (deactivate-mark))
Esta segunda función, evaluada dentro de un documento LATEX, escaneará todas las ocurrencias de mi macro
\labeldict{...}
, copiará su argumento y lo añadirá a la variable entradas-diccionario
. Pero hará lo mismo,
previamente, con la macro que introduce el nombre de la entrada, de la que sacará el texto que aparecerá «visible» en el
enlace, Con ello irá confeccionando unas preciosas listas lispianas, donde cada elemento será un enlace con la sintaxis
de Org, precedido de un guión «-» para que forme parte de una lista: "- [[./DHTC_final.pdf#marcador][nombre del
enlace]]"
.
(defun lista-entradas-diccionario () (interactive) (save-excursion (goto-char (point-min)) (while (re-search-forward (concat "\\\\" "entrada{") nil t) (copia-argumento) (re-search-forward (concat "\\\\" "labeldict{") nil t) (copia-argumento) (add-to-list 'entradas-diccionario (format "- [[./DHTC_final.pdf#%s][%s]]" (car kill-ring) (cadr kill-ring))))))
A continuación, ésta será la función que inserte la lista en el documento Org que ya tengo preparado a tal efecto. La
lista será convertida en una cadena de texto mediante mapconcat
, pero poniendo cada elemento en una línea y
separándolo por un salto de línea:
(defun inserta-lista-entradas-aquí () (interactive) (insert (mapconcat 'identity entradas-diccionario "\n\n")))
Bueno, con lo anterior ya tenemos el esquema del proceso. Ya sólo queda automatizarlo. Para ello, defino esta función
que analiza cada archivo LATEX (cada archivo es una letra del diccionario que luego compilo mediante un documento
maestro) y extrae toda la información de los marcadores que contienen dichos archivos. Para ahorrar tiempo y memoria de
proceso, le pido que descarte los archivos de backup
típicos que va creando Emacs y que terminan con el signo ~
. La
expresión directory-files
nos generará una lista con los archivos *.tex
del directorio especificado. Si el primer
argumento opcional es non-nil
, cada fichero llevará su ruta absoluta.
(defun analiza-archivos-letra-dict () (interactive) (let ((ficheros (directory-files "/home/juanmanuel/Git/dhtc/libro" t "tex" nil))) (setq ficheros (cl-remove-if (lambda (k) (string-match "tex~" k)) ficheros)) (mapcar (lambda (fichero) (save-excursion (save-window-excursion (find-file fichero) (lista-entradas-diccionario) (kill-buffer)))) ficheros)))
Y ya, la guinda final: la función que genera el índice Org o, si ya lo tenemos, lo actualiza con nuevas entradas. Y,
cómo no, las dispone ordenadas alfabéticamente gracias a org-sort-list
:
(defun actualiza-indice-web () (interactive) (setq entradas-diccionario nil) (save-excursion (save-window-excursion (analiza-archivos-letra-dict) (find-file "/home/juanmanuel/Git/dhtc/libro/index.org") (mark-whole-buffer) (kill-region (region-beginning) (region-end)) (deactivate-mark) (inserta-lista-entradas-aquí) (org-sort-list t ?a nil nil nil) (save-buffer))) (split-window-right) (other-window 1) (find-file "/home/juanmanuel/Git/dhtc/libro/index.org"))
∞
Publicado: 24/10/2019
Última actualización: 21/01/22
Esta obra está bajo una licencia de Creative Commons Reconocimiento-NoComercial 4.0 Internacional.
Notas al pie de página:
Queda para otra ocasión explicar cómo se crean en LATEX las entradas del diccionario. Pero eso ya es cosa de La lunotipia.