LoginSignup
14
6

More than 3 years have passed since last update.

pandoc-crossrefを使ったらソースコードがページを突き抜けたので自作フィルタでどうにかした

Last updated at Posted at 2020-05-01

image.png

はじめに

 Pandocで図表番号を参照するときにpandoc-crossrefを使う方は多いと思います.ところが先日,MarkdownファイルをPDFファイルに変換したときに不都合が生じました.1ページを超える長さのソースコードを記載したときに,ソースコードがページを突き抜けて読めなくなってしまうのです(図1).

 この記事では

  • pandoc-crossrefでソースコードが突き抜ける原因の調査
  • ソースコードが突き抜けないようにするフィルタ1の開発

の方法を紹介します.

突き抜けているソースコード
図1 突き抜けているソースコード

原因を調査する

MarkdownファイルはどのようにPDFファイルになっているのか

 Pandocはファイルを様々なフォーマットから様々なフォーマットに変換します2.より詳しく説明すると,まずPandocが入力ファイルをAST(Abstract Syntax Tree; 抽象構文木)に変換し,ASTを好みの内容に変換するフィルタを通り,最後にASTを出力ファイルに変換します(図2).ただしPDFに関しては一旦LaTeXファイルを出力してからlualatexでコンパイルします.

Pandocの詳細
図2 Pandocの詳細

 今回の問題は,pandoc-crossrefを使用した場合にソースコードがページを突き抜けてしまうことでした.つまりpandoc-crossrefが出力したASTに原因があると予想されます.そこでまずはPandocが出力するLaTeXファイルから原因を探し出し,次にASTの対応する部分を見つけましょう(図3).

原因の探し方
図3 原因の探し方

LaTeXファイルから原因を探す

 ソースコードを載せたMarkdownファイルをLaTeXに変換します.ただしpandoc-crossrefを使う場合と使わない場合でそれぞれLaTeXファイルを出力します.二つのファイルの差分を取ることで,pandoc-crossrefによって追加,削除及び変換されたLaTeXコマンドを見つけます.

 diffコマンドで見つけた差分は以下の通りです.両者を比較したところ,ソースコードを記載している部分がcodelistingブロックで囲まれています.この\begin{codelisting}\end{codelisting}(とcaption)を消せば複数ページにまたがってソースコードを表示できるようです.

$ diff without-filter.tex with-filter.tex
4c4,8
< {[}@lst:source\_code{]} shows source code.
---
> コード.~\ref{lst:source_code} shows source code.
> 
> \begin{codelisting}
> 
> \caption{sample}
74a79,80
> 
> \end{codelisting}

ASTから原因を探す

 LaTeXファイルのcodelistingブロックに該当する部分をASTから探します.図4はASTのファイルです.インデントも改行もなく,おぞましい内容ですね.

JSON-formatted ASTの内容
図4 JSON-formatted ASTの内容

 このファイルはJSON形式なので,論理的にはツリー状に構造化されています.そこでJSON形式に従ってもう少し見やすくしましょう.以下のコマンドでASTのファイルを見やすくします.filenameの部分は見たいJSONファイルに置き換えてください.ファイルの内容が構造化されて見やすくなります(図5).

$ python3 -c "import json, pprint; pprint.pprint(json.loads(open(\"filename\").read()))"

見やすくなったAST
図5 見やすくなったAST

 見やすくしたJSONファイルの差分を下に示します.ソースコードを含む箇所のみを抜粋しています.フィルタを使わない場合はキーcに対応する内容がソースコードとそれに関連する情報のようです.キーtに対応する内容はCodeBlockなので,まさにこのハッシュテーブルはコードブロックのためのものでしょう3
 とどのつまり,フィルタを使った場合の出力結果(下部分)をフィルタを使わない場合の出力結果(上部分)に変換する新しいフィルタを作ることで,ソースコードが突き抜けてしまう問題を解決できるでしょう.

$ diff without-filter-formatted.json with-filter-formatted.json
22,93c15,92
<             {'c': [['lst:source_code',
<                     ['c', 'numberLines'],
<                     [['startFrom', '1'], ['caption', 'sample']]],
<                    '#include <stdio.h>\n'
<                    (ソースコードの内容なので省略)
<                    '}'],
<              't': 'CodeBlock'}],
---
>             {'c': [['lst:source_code', ['listing', 'c', 'numberLines'], []],
>                    [{'c': [{'c': 'コード', 't': 'Str'},
>                            {'t': 'Space'},
>                            {'c': '1:', 't': 'Str'},
>                            {'t': 'Space'},
>                            {'c': 'sample', 't': 'Str'}],
>                      't': 'Para'},
>                     {'c': [['', ['c', 'numberLines'], [['startFrom', '1']]],
>                            '#include <stdio.h>\n'
>                            (ソースコードの内容なので省略)
>                            '}'],
>                      't': 'CodeBlock'}]],
>              't': 'Div'}],

フィルタで問題のトークンを処理する

フィルタの作り方

 今回は使い慣れたPythonでフィルタを開発することとします.フィルタを作る方法はQiitaにいくつもの記事があります45.Pythonでフィルタを作る場合はpandocfiltersモジュール6を使うと楽なようです.このモジュールでページを突き抜けないPDFファイルを作ります.
 前節で述べた通り,原因のASTトークンはtがDivで,かつtがCodeBlockのトークンを内包したものでした.このトークンをtがCodeBlockのトークンの内容に置き換えるフィルタを作ってみます.
 作ったフィルタのコードを下に示します.たった22行で作れたのは驚きです.実行権限を付けて,早速使ってみましょう

pandoc_remove_codelisting_filter.py
#!/usr/bin/env python3                                                                                                                          
# -*- coding: utf-8 -*-

from pandocfilters import toJSONFilter, CodeBlock

def myfilter(key, value, format_, meta):
    if key != 'Div':
        return

    for content in value:
        if type(content[0]) != dict:
            continue

        for sub_content in content:
            if sub_content['t'] == 'CodeBlock':
                return CodeBlock(*sub_content['c'])

def main():
    toJSONFilter(myfilter)

if __name__ == "__main__":
    main()

フィルタを使う

 作ったフィルタを使ってMarkdownファイルをPDFファイルに変換します.もちろん作ったフィルタはpandoc-crossrefの後に適用します.コマンドラインでのフィルタの適用方法を下に示します.

$ pandoc --version
pandoc 2.9.2.1
Compiled with pandoc-types 1.20, texmath 0.12.0.2, skylighting 0.8.3.4
Default user data directory: /home/kenta/.local/share/pandoc or /home/kenta/.pandoc
Copyright (C) 2006-2020 John MacFarlane
Web:  https://pandoc.org
This is free software; see the source for copying conditions.
There is no warranty, not even for merchantability or fitness
for a particular purpose.
$ pandoc-crossref --version
pandoc-crossref v0.3.6.2 git commit UNKNOWN (UNKNOWN) built with Pandoc v2.9.2.1, pandoc-types v1.20 and GHC 8.8.3
$ pandoc sample.md -o sample.pdf\
         --pdf-engine=lualatex\
         --filter=pandoc-crossref --filter=./pandoc_remove_codelisting_filter.py

 出力されたPDFファイルを図6に示します.ページをまたがってソースコードを表示できるようになりました.これで長いソースコードも安心して載せられそうです.

出力したPDFファイル
図6 出力したPDFファイル

まとめ

 pandoc-crossrefを使った場合に,PDFファイルに記載したソースコードがページを突き抜けてしまいました.そこで本稿ではまずPandocが処理するASTから原因を調査し,それから原因を含むASTトークンを変換するフィルタを開発しました.開発したフィルタpandoc_remove_codelisting_filterを使うことで,ソースコードを複数ページにまたがって載せられるようになりました.同様の問題でお困りの方は是非お使い下さい.
 また開発したフィルタはGitHubに置いておきました.pipでインストールするとフィルタをコマンドとして使えるようにしていますので,ご活用下さい.

リンク

pandoc_remove_codelisting_filter: https://github.com/Kenta11/pandoc_remove_codelisting_filter
- pip install https://github.com/Kenta11/pandoc_remove_codelisting_filterでインストールできます!

CHANGELOG

  • 5/13: Before & Afterを追加,注釈3を修正.

  1. フィルタはPandocでドキュメントを変換するためのプログラムです.『原因を調査する』で詳しく述べています. 

  2. すごい数ありますhttps://pandoc.org/ 

  3. 『ものでしょう』と述べているのは,つまり本当の仕様を知らないためです.このASTのドキュメントは無いようです.AST Documentation? #3262Haskellのデータ構造で表現した仕様がありました.Text.Pandoc.Definition 

  4. @takada_at, "PythonでPandocのフィルターを書く," https://qiita.com/takada-at/items/187923e57071c0aa73a7

  5. @irisTa56, "Pandoc の filter として panflute を使ってみる," https://qiita.com/irisTa56/items/34c78c92b0b0e0be90a6

  6. jgm, "pandocfilters," https://pypi.org/project/pandocfilters/ 

14
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
6