はてなブログで mermaid を使う (2023/3/7追記あり)

天文ブログの方でフローチャート風の図を描くのに Mermaid を使ってみたのだけど、うまくいかないことがあったので回避方法をメモ。

Mermaid はダイアグラムやチャートを Markdown 風の記法で記述して、JavaScript で図として描画する仕組みで、数式における MathJax に相当するもの。

はてなブログの場合、数式に関しては「はてな記法」で書けば事足りるのだが、Marmaid については識別用のタグを付けたブロックに marmaid の記法で記述して、その後ろに JavaScript のコード断片をコピペしておくことで、閲覧者のブラウザ上で JavaScript を動かしてブロックを図(SVG)に変換するというやり方になる。たとえば以下のように(ブログをはてな記法で書く場合)。

><pre class="mermaid">
graph TD
    Hello --> World
</pre><script type="module">import mermaid from 'https://unpkg.com/mermaid@9/dist/mermaid.esm.min.mjs';mermaid.initialize({ startOnLoad: true });</script><

これは以下のように表示される。

graph TD
    Hello --> World

はてなブログはデフォルトで編集画面で入力した論理行を p タグで囲んで表示してしまうため、mermaid をそのまま書くとエラーになってしまうので ><pre class="mermaid">...</script>< のように「pタグ停止記法」を使用している。*1

しかし、これでうまくいかない場合がある。たとえば以下。

><pre class="mermaid">
graph TD
    iPhone --> Xperia
</pre><script type="module">import mermaid from 'https://unpkg.com/mermaid@9/dist/mermaid.esm.min.mjs';mermaid.initialize({ startOnLoad: true });</script><

ラベル名が変わっただけなのだが、これは描画に失敗してしまう。

graph TD
    iPhone --> Xperia

これはブログのHTMLに変換される際に「iPhone」「Xperia」といった単語に「はてなキーワード」のマークアップが追加されてしまうからだ。上の例だと pre タグのブロックは以下のように変換されてしまう。

<pre class="mermaid">graph TD
    <a class="keyword" href="http://d.hatena.ne.jp/keyword/iPhone">iPhone</a> --&gt; <a class="keyword" href="http://d.hatena.ne.jp/keyword/Xperia">Xperia</a>
</pre>

「pタグ停止記法」はあくまで p タグの挿入を停止するだけで、他のタグは挿入されてしまう。「スーパーpre記法」と同等のブロック内に一切タグが挿入されない記法があればよいのだが…

そこでスーパーpre記法の中に mermaid を書きたいのだが、mermaid のスクリプトは class="mermaid" の付いた div または pre の直下のテキストしか見てくれないようで、スーパーpre記法をpタグ停止記法の ><pre class="mermaid">...</pre>< で囲んでもダメだった。

そこで以下のようにスクリプトを追加することで解決した。

><pre class="mermaid">
graph TD
    iPhone --> Xperia
</pre><script>document.querySelectorAll('.mermaid:not([processed])').forEach((e) => { e.innerHTML = e.textContent; e.setAttribute('processed', 'true'); }) </script><script type="module">import mermaid from 'https://unpkg.com/mermaid@9/dist/mermaid.esm.min.mjs';mermaid.initialize({ startOnLoad: true });</script><
graph TD
    iPhone --> Xperia

追加のスクリプトでは mermaid クラスの pre タグの内容をタグなしのテキストで置換する。また、このスクリプトがページ内に複数ある場合に処理が繰り返されないように*2 processed 属性を追加して、この属性があるタグは置換の対象にしないようにしてある。

スクリプトはロードされた時点で実行されるため、スクリプトが書かれた場所より手前にある pre タグしか置換されない。そのため mermaid クラスの pre タグの直後、mermaid のスクリプトより手前に書くようにする。

追記(2023-03-07): mermaid 記法(未満)、ありました…

なんと公式に mermaid 記法(未満)ありました。いつからあったんだろう?

スーパーPREの言語指定の部分に「mermaid」を指定しするだけ!と、言いたいところですが、mermaid の JavaScript は別途ロードしなくてはなりません。そこが「(未満)」と書いた所以です。

*1:script タグを「pタグ停止記法」の中に含めて </pre> に改行なしで続けて記述しているのは、はてなブログの記事一覧で並べて表示される記事の冒頭部分に mermaid がある時に図が正しく描画されることを期待してだが、確実な効果があるかは不明。

*2:置換が繰り返されても同じ内容になるので表示は変わらないが処理が無駄になる