Pandoc Filter

Pandoc ist ein Tool um Textdateien in unterschiedliche Formate zu wandeln. Es ist sehr praktisch um Markdown Dateien in HTML umzuwandeln, doch es kann noch viel mehr. Vor ein paar Tagen kam ich auf die Idee mit jTab eine Webseite zu basteln. Mir wurde es recht schnell zu kompliziert, das ganze von Hand zu machen und wollte es mit Pandoc automatisieren.

Die einfachste Art Pandoc zu erweitern sind Filter. Hier ist ein gutes Tutorial, auch ein Beispiel mit Python, doch es bleiben noch viele Fragen offen. Wir starten mit dem Modul pandocfilters und einer einfachen Struktur.

# filter.py
from pandocfilters import toJSONFilter, Para, RawBlock

def jtab_filter(key, value, f, meta):
    return None

if __name__ == "__main__":
    toJSONFilter(jtab_filter)

Um den Filter auszuprobieren brauchen wir diese zwei Befehle:

pandoc  -t native --filter ./filter.py test.md
pandoc  -t html --filter ./filter.py test.md

Der erste zeigt, wie Pandoc die Textdatei ausspuckt und die zweite zeigt den Output in HTML. Als letztes brauchen wir noch eine Markdown Datei zum Testen:

# Heading

Test

!Melody $A 3 3 5 7 | $6 3 3 $5 5 7 | $E 5 5 $A 5 7 | $E 1 1 $A 7 5 ||

Filter

Solange der Filter ein None zurueckgibt, wie in unserem Beispiel, wird der Text unveraendert ausgegeben. Das heisst unser Filter macht im Moment nichts. Wenn wir aber nach einem selbstgebastelten Keyword suchen wollen muessen wir erstmal ein paar Regeln aufstellen:

  • Unser Keyword muss immer am Anfang der Zeile stehen
  • Es fängt mit einem Ausrufezeichen an, gefolgt von einem Wort
  • Nach einem Leerzeichen kommt in der restlichen Zeile der Inhalt, den wir bearbeiten wollen

Pandoc übergibt jede Zeile einzeln an die Funktion jtab_filter mit Formatierung und in Wörter geteilt. Zuerst müssen wir prüfen, ob die Zeile ein Paragraph ist. Um das erste Wort zu bekommen können wir das erste Item in value nehmen und daraus den Key c. Ich denke das c steht für Content.

import re

def jtab_filter(key, value, f, meta):
    if key == 'Para':
        first_word = value[0].get('c')
        regex = re.compile(r"!([A-Za-z]+)")
        match = regex.search(first_word)

Danach benutzen benutzen wir regex um unser Keyword zu finden. r"!([A-Za-z]+)" sind wie unsere Regeln oben, nur in eine Regular Expression gegossen.

Der naechste Schritt ist zu prüfen ob in match auch etwas drin ist. Wenn nicht geben wir einfach nichts zurück (None) und die Zeile bleibt unverändert. Wenn aber ein Match gefunden wurde dann geht es weiter.

if match:
    line_content = ' '.join([x['c'] for x in value[1:] if 'c' in x])
    keyword = match.group(1)
    if tab_class == 'Tabonly':
        tabonly_template = '<div class="jtab tabonly">{}</div>'
        return [RawBlock('html', tabonly_template.format(line_content))]

In line_content basteln wir uns die gesamte Zeile, ohne das Keyword wieder zusammen. Die Leerzeichen (Space) überspringen wir einfach, was wir mit dem join Statement wieder beheben. Vielleicht gibt es eine bessere Lösung, aber das ging einfach am schnellsten.

Jetzt kann man noch das Keyword auslesen, netterweise haben wir das im Regex durch die Klammern in Gruppe 1 geschoben. Fuer jedes Keyword kann man ein anderes Template benutzen. In der letzten Zeile geben wir einen RawBlock zurück, also eine Zeile die Pandoc nicht formatieren soll. Darin ist genug HTML um einen Tabulatur Block fuer jTab zu formatieren.

Damit ist der grundlegendste Teil des Filters fertig. Wenn etwas nicht stimmt ist es nicht so ganz einfach ihn zu debuggen. Bisher habe ich es am besten mit raise Exception(variable) geschafft, denn print funktioniert nicht. Überhaupt sollte man nie ein print verwenden, da es Pandoc durcheinander bringt.

Wie man den Rest der Seite baut werde ich in einem anderen Blogpost beschreiben, da das Projekt noch nicht fertig ist.