Aiemmassa postauksessa tarkastelin Pandocin AST:tä. Siihen voi esimerkiksi
tallentaa CodeBlock
-noden, johon sisältyy ohjelmakoodia. Esittelen tässä
lyhyesti, kuinka voidaan vaikuttaa yksityiskohtaisesti siihen, miltä AST:stä
renderöity lopputulos näyttää LaTeXilla. Käytännön tasolla, CodeBlock
pitää
muuttaa RawBlock
-nodeksi käyttämällä Pandocin suodattimia.
Käytetään tässä esimerkkinä seuraavaa SQL-kyselyä:
```{#sql1 .sql caption="Hankala SQL-kysely: 1+1."}
SELECT 1+1";
```
Tämä näkyy Pandocin AST:ssä tallennettuna:
[CodeBlock ("sql1",["sql"],[("caption","Hankala SQL-kysely: 1+1.")]) "SELECT 1+1;"]
Teknisesti siinä on kaikki tarpeellinen. sql1
on lohkon id-numero ja ["sql"]
viittaa periaatteessa lohkon luokkiin, käytännössä sillä tunnistetaan mikä
ohjelmointikieli on kyseessä. Id-numeron ja luokkien jälkeen annetaan key-value
arvoja, jotka voivat olla mitä tahansa. Esimerkiksi caption
-avain on minun
itseni keksimä eikä se tee yhtään mitään, vielä, mutta se voisi esimerkiksi
kirjoittaa listauksen otsikon kun node renderöidään LaTeX-formaattiin. Lopuksi
tulee varsinainen lohkon sisältö.
Nyt tarkoituksena olisi saada tämä AST:ssä näkyvä node kirjoitettua esimerkiksi LaTeX-formaattiin. Pandocissa on valmiiksi LaTeX-kirjoittaja, ja tämä node sillä renderöitynä näyttää seuraavalta.
\hypertarget{sql1}{
\label{sql1}}
\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \DecValTok{1}\NormalTok{+}\DecValTok{1}\NormalTok{;}
\end{Highlighting}
\end{Shaded}
Komennot, joilla konversiot siis tehtiin, ovat
pandoc -f markdown -t native codeblock.md > codeblock.native
pandoc -f native -t latex codeblock.native > codeblock.tex
Haluaisin käyttää LaTeXissa tuon lohkon renderöimiseen jotakin muuta ympäristöä
kuin tarjottua Shaded
ja Highlighting
. Esimerkiksi minted
. Tätä tavoitetta
voisi lähestyä varmaan useammallakin eri tavalla. Ensimmäisenä tulee mieleen
että jossakin syvällä lähdekoodin syövereissä täytyy olla
Haskellilla naputeltuna Pandocin LaTeX-writer,
jota voisi muokata. Tällaisia kirjoittajia voi varmasti myös tehdä itse uusia.
Tämä tuskin kuitenkaan on oikea tapa edetä, sillä pääpiirteittäin LaTeX
kirjoitetaan aivan oikein. Tässä halutaan nyt uudelleenkirjoittaa vain
yksittäinen node.
AST:n nodesta pitäisi päästä vaikkapa seuraavaan:
\begin{listing}
\caption{Hankala SQL-kysely: 1+1.}
\begin{tcolorbox}
\begin{minted}{sql}
SELECT 1+1;
\end{minted}
\end{tcolorbox}
\label{listing:sql1}
\end{listing}
Kuten edellisessä kirjoituksessa esiselvityksessä kävi ilmi, ei Pandocin AST:ssä
ole kaikille mahdollisille rakenteille omia tyyppejä, joten nähdäkseni ainoa
järkevä vaihtoehto on vaihtaa CodeBlock
-tyyppi RawBlock
-tyyppiin,
parametrilla latex
, sekä tallentaa LaTeX-koodi suoraan siihen. Eli, uusi solmu
AST-puussa olisi seuraavanlainen:
[RawBlock (Format "latex") "
\\begin{listing}
\\caption{Hankala SQL-kysely: 1+1.}
\\begin{tcolorbox}
\\begin{minted}{sql}
SELECT 1+1;
\\end{minted}
\\end{tcolorbox}
\\label{listing:sql1}
\\end{listing}"]
Pandoc tukee suodattimia, joiden tarkoituksena on muokata AST:tä ennen sen kirjoittamista. Suodattimia voi kirjoittaa millä tahansa ohjelmointikielellä, ja ne ottavat sisälle AST:n JSON-version ja antavat ulos uuden version AST:stä, jossa filtteriä on käytetty.
Tässä tapauksessa AST on JSON-formaatissa:
{
"blocks": [
{
"t": "CodeBlock",
"c": [
["sql1", ["sql"], [["caption", "Hankala SQL-kysely: 1+1."]]],
"SELECT 1+1;"
]
}
],
"pandoc-api-version": [1, 17, 5, 1],
"meta": {}
}
Tehtäväksi jää tehdä esimerkiksi Python-skripti, joka lukee JSON-tiedoston ja muokkaa sitä. Yksinkertainen implementaatio tähän olisi esimerkiksi:
import json
import sys
__template__ = """
\\begin{listing}
\\caption{% raw %}{%s}{% endraw %}
\\begin{tcolorbox}
\\begin{minted}{% raw %}{%s}{% endraw %}
%s
\\end{minted}
\\end{tcolorbox}
\\label{listing:%s}
\\end{listing}
"""
ast = json.loads(sys.stdin.read())
for block in ast["blocks"]:
if block["t"] == "CodeBlock":
((identifier, classes, kvpairs), code) = block["c"]
block["t"] = "RawBlock"
args = dict(kvpairs)
params = (args["caption"], classes[0], code, identifier)
block["c"] = ["latex", __template__.strip() % params]
print(json.dumps(ast))
Nyt saamme AST:n oikeaan muotoon
cat codeblock.json | ./pandoc-codeblock-minted.py | python -m json.tool
{
"blocks": [
{
"t": "RawBlock",
"c": [
"latex",
"\\begin{listing}\n\\caption{Hankala SQL-kysely: 1+1.}\n\\begin{tcolorbox}\n\\begin{minted}{sql}\nSELECT 1+1;\n\\end{minted}\n\\end{tcolorbox}\n\\label{listing:sql1}\n\\end{listing}"
]
}
],
"pandoc-api-version": [1, 17, 5, 1],
"meta": {}
}
Ja nyt kun käytetään tätä skriptiä Pandocin suodattimena, saadaan odotettu lopputulos:
pandoc -f markdown -t latex --filter=./pandoc-codeblock-minted.py codeblock.md
\begin{listing}
\caption{Hankala SQL-kysely: 1+1.}
\begin{tcolorbox}
\begin{minted}{sql}
SELECT 1+1;
\end{minted}
\end{tcolorbox}
\label{listing:sql1}
\end{listing}
Suodatin toimii, mutta se on hieman purkkavirityksen oloinen. Vähintäänkin pitäisi tarkastella, onko kaikki parametrit annettu ja sen perusteella muokata formaattia. Esimerkiksi, jos käyttäjä ei ole antanut otsikkoa, ohjelmointikielen nimeä tai tunnistetta, pitäisi vasteesta poistaa asianmukaiset rivit. Lisäksi pitäisi jotenkin kontrolloida, että suodatinta käytetään ainostaan mikäli AST:tä ollaan kirjoittamassa LaTeX-formaattiin.
Löytyy myös valmiita projekteja kuten pandoc-filters ja panflute joita ilman muuta kannattaa hyödyntää, jos suodattimia kirjoittaa JSON-formaatissa esimerkiksi Pythonilla. Valmiitakin suodattimia löytyy, ja esimerkiksi minted-ympäristön kirjoittamiseen on valmis suodatin.
Kun AST:tä parsitaan JSONista, ei ohjelmointikielellä periaatteessa ole merkitystä. Voi käyttää sitä ohjelmointikieltä minkä parissa parhaiten viihtyy. Jotkin ohjelmointikielet voivat olla toista parempia JSON-datan käsittelyssä. Python on tässä varmastikin edukseen.
Eräs mielenkiintoinen ajatus olisi ajaa dokumentissa olevia koodiblokkeja sitä mukaan kun niitä tulee AST:ssä vastaan. Tämä edellyttäisi sopivan eristetyn leikkikentän rakentamista, joten suodatin on silloin järkevintä toteuttaa sillä ohjelmointikielellä mitä aikoo suorittaa.
Pandociin on rakennettu myös Lua-suodattimet. Lua on käännetty ohjelmaan sisälle, mistä on tiettyjä tehokkuusetuja, sillä Lua-suodattimilla ei JSON-tiedostoa tarvitse parsia. Jos Lua-ohjelmointikieltä sattuu osaamaan tai sitä haluaa oppia, niin Lua olisi se parempi tapa rakennella suodatin.