golden-luckyの日記

ツイッターより長くなるやつ

PandocをreSTのリストテーブルに対応させる

Python界隈でよく見かける構造化文書のための記法として、reStructuredText(以降はreSTと書きます)があります。

軽量マークアップ言語などと呼ばれることもありますが、reSTはかなり高度な表現力がある記法です。 その記法をパースするために標準で使われているのはDocutilsという仕組みです。ただ、DocutilsはreST専用ではなく、他の記法のパーサを実装することもできるらしいです。 その意味でDocutilsは、Pandocと同じく、内部の抽象的なデータ構造へと記法を押し込めるツールだといえる気がします。

Docutilsについては『マスタリングDocutils』に詳しいので興味がある方は購入しましょう。

今日はDocutilsのことは忘れて、reSTの記法をPandocで読み込み、Pandocの抽象データ型(Pandoc構造)に押し込める話をします。

reSTのリストテーブル

reSTにはリストテーブル(List Table)という記法があります。 正確に言うとこれはreST本来の記法ではなく、reSTに備わっているディレクティブという仕組みで定義された拡張です。 ちなみにディレクティブとは、思いっきり単純にいうと、「ドキュメントの構造を作り出す局所ローカルな記法」を定義するためにreSTに用意されている仕掛けです。

リストテーブルは、表(テーブル)を2階層の箇条書きで書けるという便利な記法です。 上記の公式ドキュメントにはこんな例が載っています。

.. list-table:: Frozen Delights!
   :widths: 15 10 30
   :header-rows: 1

   * - Treat
     - Quantity
     - Description
   * - Albatross
     - 2.99
     - On a stick!
   * - Crunchy Frog
     - 1.49
     - If we took the bones out, it wouldn't be
       crunchy, now would it?
   * - Gannet Ripple
     - 1.99
     - On a stick!

上記のリストテーブルは、こんな感じのシンプルなテーブルとして最終的にレンダリングされることが想定されています。

Frozen Delights!
Treat Quantity Description
Albatross 2.99 On a stick!
Crunchy Frog 1.49 If we took the bones out, it wouldn't be crunchy, now would it?
Gannet Ripple 1.99 On a stick!

PandocのreST Readerでリストテーブルは読めるか

さて、PandocにはreSTのReaderもあります。 わりといろいろなディレクティブにも対応しているのですが、リストテーブルについては2017年5月ごろまで長らく未対応でした。 試しに当時のpandocコマンド(バージョン1.17.2がたまたま手元で利用可能でした)で上記の例を読み込んでみると、こんな感じに無視されてしまいます。 (pandocでは、-t nativeとすることで、Haskellの代数的データ型(をshowしたもの)の生の姿を見られます)

$ pandoc -f rst -t native temp.rst
pandoc: ignoring unknown directive: list-table "source" (line 19, column 1)
[]

一方、最近のpandocコマンドでは、こんなふうにPandoc構造として読み取ってくれます。

$ pandoc -f rst -t native temp.rst
[Table [Str "Frozen",Space,Str "Delights!"] [AlignDefault,AlignDefault,AlignDefault] [0.0,0.0,0.0]
 [[Plain [Str "Treat"]]
 ,[Plain [Str "Quantity"]]
 ,[Plain [Str "Description"]]]
 [[[Plain [Str "Albatross"]]
  ,[Plain [Str "2.99"]]
  ,[Plain [Str "On",Space,Str "a",Space,Str "stick!"]]]
 ,[[Plain [Str "Crunchy",Space,Str "Frog"]]
  ,[Plain [Str "1.49"]]
  ,[Plain [Str "If",Space,Str "we",Space,Str "took",Space,Str "the",Space,Str "bones",Space,Str "out,",Space,Str "it",Space,Str "wouldn't",Space,Str "be",SoftBreak,Str "crunchy,",Space,Str "now",Space,Str "would",Space,Str "it?"]]]
 ,[[Plain [Str "Gannet",Space,Str "Ripple"]]
  ,[Plain [Str "1.99"]]
  ,[Plain [Str "On",Space,Str "a",Space,Str "stick!"]]]]]

しかし、これには実は制限があります。 リストテーブルでは、テーブルのヘッダとなる行の数を:header-rows:で指定できるのですが、これを2にしても、常に最初の1行だけがヘッダになります。 たとえば次のようなリストテーブルをPandocでMarkdownに変換してみると…、

.. list-table:: 
   :widths: 15 10 30
   :header-rows: 2

   * - Treat
     - Quantity
     - Description
   * - Albatross
     - 2.99
     - On a stick!

こうにしかなりません。

$ pandoc -f rst -t markdown temp.rst
  Treat           Quantity   Description
  --------------- ---------- -----------------------------------------------------
  Albatross       2.99       On a stick!

Markdownにヘッダ行が複数のテーブルがないからだろ、と思うかもしれませんが、HTMLでも同じです。

$ pandoc -f rst -t html temp.rst
<table>
<thead>
<tr class="header">
<th>Treat</th>
<th>Quantity</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Albatross</td>
<td>2.99</td>
<td>On a stick!</td>
</tr>
</tbody>
</table>

この連載をずっと読んでもらっていればわかると思いますが、これは「複数行ヘッダのテーブル」を表す型がPandoc構造にないからです。 どんなにがんばってReaderを実装しても、このリストテーブルをreSTの意図通りに読むこと、つまりDocutilsと同じように読むことはできないのです。

宣伝

最後に宣伝ですが、このPandocのreSTリストテーブル対応、ぼくがやりました。 何もコメントせずにいきなりPRを出したらjgmから瞬時に怒られが発生して青くなったのは懐かしい思い出です(最初はPRを出すつもりなかったけど操作ミスでこうなった)。

そもそもなんで実装したかといったら、『Goならわかるシステムプログラミング』の原稿がreSTで、アスキー.jp連載時はこれをPandocでHTMLにしてアスキーさんに入稿してたんですが、そのときにPandocがリストテーブル未対応で困ったからなのでした(なおSphinxを使わなかったのは、入稿仕様のHTMLにするPandocのテンプレートをすでに作っていて、それを使いまわしたかったからです)。

こんな感じに編集ツールを作るようなお仕事もできるので、そうしたお仕事があったらご連絡ください。