Cuaderno de GNUtas

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


Índice general

Acerca de...

Esta obra está bajo una licencia de Creative Commons Reconocimiento-NoComercial 4.0 Internacional.

Notas al pie de página:

1

Queda para otra ocasión explicar cómo se crean en LATEX las entradas del diccionario. Pero eso ya es cosa de La lunotipia.

© Juan Manuel Macías
Creado con esmero en
GNU Emacs