
Las tablas de rebajas son infernales:
- No puede escribir texto en celdas más largas que un par de palabras, y menos aún una lista.
- Si el dialecto permite el párrafo 1, no es conveniente formatearlo.
- Si las celdas no están alineadas, la tabla no se puede leer.
- No hay soporte para tablas del mismo tipo y automatización, como la numeración de líneas.
Es hora de escribir un filtro para Pandoc que dibuje tablas de YAML estructurado, con numeración de filas, orientación horizontal, patrones gráficos y, al mismo tiempo, descubra cómo escribir filtros Lua.
Usualmente escribo textos en Markdown y los convierto al formato de destino usando Pandoc. Este es un programa que convierte documentos entre formatos, por ejemplo, desde Markdown puede obtener HTML y otro dialecto de MD, DOCX y PDF (más de 30 formatos de entrada y más de 50 formatos de salida). Pandoc Markdown tiene muchas extensiones convenientes para enlaces, notas al pie, firmas, fórmulas.
Pandoc funciona como una composición de funciones (se habría escrito en Haskell): un formato de entrada específico → representación abstracta de un documento → un formato de salida específico. Una representación abstracta se puede modificar utilizando filtros escritos en Lua. Los filtros no necesitan saber sobre el formato de salida, pero pueden tenerlo en cuenta.
Nuestro filtro buscará bloques abstractos de código en la table
lenguaje condicional en la representación abstracta, leerá YAML dentro de ellos y generará representaciones abstractas de tablas que Pandoc producirá en el formato de destino.
pandoc --lua-filter table.lua input.md -o output.html
¿Cuáles son las alternativas y por qué son peores?
- Las tablas HTML solo funcionan en Markdown y se convierten solo a HTML; solo se resuelve el problema del formato enriquecido en las celdas.
- Los generadores de tablas requieren cambiar de un editor de texto; es inconveniente editar el contenido de las celdas en ellos ( ejemplo ).
- Los complementos de los editores ( Emacs Org-Mode , VIM plugins ) no son universales y no siempre están disponibles.
Por el contrario, pandoc-crossref
y todos los bollos de pandoc-crossref
funcionan con el filtro para tablas de resumen. El filtro también se puede utilizar para generar tablas de Markdown estándar especificando el formato de salida apropiado. De las desventajas:
- Las celdas no se pueden fusionar; Pandoc no es compatible con esto (todavía).
- Para las tablas horizontales, la estilización debe hacerse usando el formato de salida, por ejemplo, a través de CSS.
La descripción de la tabla incluye tres partes:
Estructura de la mesa
Una lista ordenada de gráficos (columnas):
- Como mínimo, la columna debe tener un título.
- Para poder reorganizar las columnas sin tocar los datos, se debe especificar el atributo de registro que se muestra en la columna (
id
). - Las columnas especiales no tienen identificación, pero tienen una descripción de cómo llenarlas. Primero necesita un número de serie (
special: number
). - Alineación de columna (
align
).
Además, la mesa puede ser vertical u horizontal ( orientation
). En el último caso, los gráficos serán filas.
Propiedades de la tabla: ID para enlaces ( id
) y firma ( caption
). Pandoc le permite firmar tablas, pero no bloques de código.
Datos en forma de una matriz de diccionarios YAML.
La estructura puede ser común a varias tablas, por lo que puede describirla directamente con la tabla, y una vez en los metadatos (primer plano), y luego consultar la plantilla con nombre.
Plan de Implementación:
A partir de los metadatos del documento, formamos un diccionario de plantillas.
Para cada bloque de código con la table
clase:
- Analizamos las tablas YAML.
- Si se especifica una plantilla, la tomamos del diccionario; de lo contrario, completamos la plantilla de YAML.
- Completamos las propiedades individuales de la tabla de YAML.
- Formamos entradas de tabla a partir de YAML (un registro es una fila en una tabla normal o una columna en una horizontal).
- "Dibujamos" una tabla de acuerdo con una plantilla, propiedades y registros.
El nivel superior se implementa tal como está escrito (todo el código está disponible en el enlace al final del artículo):
function Pandoc(doc) local meta_templates = doc.meta['table-templates'] if meta_templates then for name, value in pairs(meta_templates) do templates[name] = parse_template(value) end end local blocks = pandoc.walk_block(pandoc.Div(doc.blocks), { CodeBlock = create_table }) return pandoc.Pandoc(blocks, doc.meta) end
La función parse_template()
convierte ligeramente el formato de metadatos. Pandoc representa sus valores como MetaInline
MetaBlock
y MetaInline
. Se forman líneas simples pandoc.utils.stringify()
(por ejemplo, orientación) o elementos visuales (por ejemplo, un bloque de texto en el encabezado de la columna).
Sobre depuración. Hay muchos ejemplos en la documentación de Pandoc, pero los tipos no son muy detallados. Para los filtros de depuración, es conveniente tener una función de volcado variable. Las bibliotecas serias imprimen demasiados detalles, prefiero una de las opciones simples.
Funciones para convertir metadatos a elementos del documento. local function to_inlines(content) if content == nil then return {} elseif type(content) == 'string' then return {pandoc.Str(content)} elseif type(content) == 'number' then return to_inlines(tostring(content)) elseif content.t == 'MetaInlines' then inlines = {} for i, item in ipairs(content) do inlines[i] = item end return inlines end end local function to_blocks(content) if (type(content) == 'table') and content.t == 'MetaBlocks' then return content else return {pandoc.Plain(to_inlines(content))} end end
Se create_table()
función create_table()
para cada bloque de código en triple backtics.
Solo nos interesan los bloques de código "en el lenguaje" de la table
:
if not contains('table', block.classes) then return block end
Para analizar YAML dentro de un bloque de código, creamos un documento que consta solo de metadatos YAML, lo analizamos con Pandoc y dejamos solo metadatos:
local meta = pandoc.read('---\n' .. block.text .. '\n---').meta
A continuación, desde meta
se lee meta
enlace a una estructura de plantilla o tabla y las propiedades de una tabla específica.
La función fill_table()
lee de meta
en los atributos especificados en la descripción del gráfico. En la misma etapa, si la columna se marca como especial, se genera su contenido:
local data = {} for i, serie in ipairs(template.series) do if serie.special == 'number' then data[i] = to_blocks(#datum + 1) else data[i] = to_blocks(item[serie.id]) end end
La función format_table()
forma la matriz de celdas resultante según la orientación de la tabla y crea un objeto de tabla abstracta. Cabe señalar que si los anchos o encabezados se deben configurar para todas las columnas o para ninguna, de lo contrario, Pandoc simplemente no creará una tabla.
El script terminado se puede poner en ~/.local/share/pandoc
(el directorio de datos de ~/.local/share/pandoc
) para acceder a él por nombre desde cualquier lugar.
PS
Acerca de la contabilidad de los filtros de formato de salida. Por ejemplo, escribo spoilers en Pandoc como este:
::: {.spoiler title=""} . :::
No hay spoilers en el modelo de documento de Pandoc, por lo que el filtro debe producir bloques sin procesar aproximadamente de la siguiente manera. Por supuesto, el código real ( spoiler.lua
) debe tener en cuenta el formato de salida a través de la variable FORMAT
, y no mecánicamente: el fragmento a continuación produce bloques sin formato en HTML, aunque el formato de salida es rebajado.
function Div(el) if not el.attr or not contains('spoiler', el.attr.classes) then return el end local title = el.attr.attributes['title'] or '' table.insert(el.content, 1, pandoc.RawBlock('html', '<' .. 'spoiler title="' .. title .. '">', 'RawBlock')) table.insert(el.content, pandoc.RawBlock('html', '<' .. '/spoiler>', 'RawBlock')) return el.content end
Referencias