List continuationでAsciidoctorの順序付きリストの採番を崩さないようにする


はじめに

マークアップ言語で手順を記述する際に順序付きリストは便利ですよね。
しかし、代表的なマークアップ言語であるMarkdownでは、順序付きリストの間にコードブロックやテーブルを挿入すると採番が1から振り直されてしまうという悲しみを背負っています。
一方で、Asciidoctorというマークアップ言語にはそのような悲しみを克服するList continuationという機能が用意されています。
本記事では、List continuation機能で順序付きリストの採番を崩さないようにする例を紹介します。

環境情報

$ asciidoctor --version
Asciidoctor 1.5.6.2 [http://asciidoctor.org]
Runtime Environment (ruby 2.5.0p0 (2017-12-25 revision 61468) [x86_64-darwin17]) (lc:UTF-8 fs:UTF-8 in:- ex:UTF-8)

List continuation とは

まず、公式の説明を引用させていただきます。

To add additional paragraphs or other block elements to a list item, you must “attach” them (in a series) using a list continuation. A list continuation is a + symbol on a line by itself, immediately adjacent to the block being attached.

block とは Asciidoctor の文書を構成する要素です。例えば、以下は block です。

  • コードブロック
  • パラグラフ

つまり、表やコードブロックなどを含めてリストの1項目とする機能です。

NG例

最初に、順序付きリストの採番が崩れてしまう例を示します。
コードは以下です。

. foo
|===
||0|1
|0|0|1
|1|1|0
|===

. bar

. baz

表示は以下です。

barに1が採番されてしまいました。

List continuationで採番を崩さない例

次に、順序付きリストの採番を崩さない例を示します。
コードは以下です(2行目に+が追加されただけです)。

. foo
+
|===
||0|1
|0|0|1
|1|1|0
|===

. bar

. baz

表示は以下です。

barにきちんと2が採番されました。
図、コードブロック、パラグラフも同様に採番を崩さないようにできます。

少しマニアックなおまけ

Asciidoctorは文書に属性(環境変数のようなもの)を定義する機能があります。
例えば、以下のコードは pi という属性に 3.14 をセットし、それを参照した文を表示させています。

:pi: 3.14
. 円の半径 `r` を測ります。
. 円の面積 `S = {pi} * r * r` を計算します。
. 円の円周 `L = 2 * {pi} * r` を求めます。

表示は以下です。

上記のように記述することで、円周率を 3 として計算させるように変更する際、1行目の属性 pi の値を変更するだけでよくなります。

さて、稀なケースではありますが、属性の定義を順序付きリストの中に組み込みたい場合があります。
あまりうまい例ではないですが、以下のコードを考えます。

. 小数部分 `r` と 次数 `n` を標準入力で受け取ります。

ifeval::[{precise} == true]
. 2次の項まで近似します、というメッセージを標準出力します。
:expr: 1 + nr + n(n-1)/2*r*r
endif::[]
ifeval::[{precise} == false]
. 1次の項まで近似します、というメッセージを標準出力します。
:expr: 1 + nr
endif::[]

. `y = {expr}` を求めます。

. `y` を標準出力します。

上記は属性 precisetrue ならば2次の項まで近似する項目を、 false ならば1次の項まで近似する項目を表示させたかったコードです(ちなみに、 ifeval は想像の通り、条件分岐文です)。
しかし、以下のように属性の定義式がそのまま表示され、属性の定義もされていません( precisetrue とします)。

これもList continuationすれば解決するのか?と思い、以下のコードにしてみます(属性の定義式の前の行に+を追加した)。

. 小数部分 `r` と 次数 `n` を標準入力で受け取ります。

ifeval::[{precise} == true]
. 2次の項まで近似します、というメッセージを標準出力します。
+
:expr: 1 + nr + n(n-1)/2*r*r
endif::[]
ifeval::[{precise} == false]
. 1次の項まで近似します、というメッセージを標準出力します。
+
:expr: 1 + nr
endif::[]

. `y = {expr}` を求めます。

. `y` を標準出力します。

以下のように属性の定義式は表示されなくなりましたが、属性は定義されていないままです。

リストの中で属性の定義はできないのでしょうか。
実は、以下のようにインラインで属性を定義することで実現できます。

. 小数部分 `r` と 次数 `n` を標準入力で受け取ります。

ifeval::[{precise} == true]
. 2次の項まで近似します、というメッセージを標準出力します。
{set:expr:1 + nr + n(n-1)/2 r*r}
endif::[]
ifeval::[{precise} == false]
. 1次の項まで近似します、というメッセージを標準出力します。
{set:expr:1 + nr}
endif::[]

. `y = {expr}` を求めます。

. `y` を標準出力します。

出力は以下のとおりです。

期待通りの結果が得られました。
これは、属性の定義式がブロックでないことに起因すると思われます。

参考ページ