retarfiの日記

自然言語処理などの研究やゴルフ、音楽など。

JupyterでToC&HTMLがうまく機能しない

Jupyterで作成したドキュメントを、HTMLで出力して他の人と共有することで、いちいちPowerPointなどスライドにせずとも簡単に作成できます。

特にExtentionであるTable of Contents(ToC)によって、Markdownによる目次が追加でき、より見やすいファイルを作ることができます。
セルからPythonコードによってMarkdownを出力しない限りは。
例えば、2つのMarkdownセルに以下のように記述したファイルをHTMLに出力します。

# First Cell

## First-sub Cell

そしてコマンド jupyter nbconvert --to html_toc hoge.ipynb でHTMLにします。
すると、以下の画像のようなHTMLファイルが作られます。

f:id:retarfi:20211104151722p:plain

左側のサイドバーに目次が作られ、クリックするとその場所に飛ぶことができます。便利です。
ちなみに、大量の画像を(サブディレクトリに格納することなく)1つのファイルにまとめるには --ExtractOutputPreprocessor.enabled=False オプションをつけます。
今まで散々探していたのに、今日初めて知りました。

で、ここまでは良いのですが、次にPythonコードでもMarkdownを出力したくなったとします。

f:id:retarfi:20211104152059p:plain

Jupyter上ではナンバリングが正しくされ、Table of Contents上でも正しく表示されます。
しかし、HTMLにconvertすると

f:id:retarfi:20211104152225p:plain

1.1にあるべきのFirst-sub Cellが飛ばされ、左のサイドバーにも表示されません。
これは非常に不便で、for文等で機械的Markdownを出力する時に困ります。

github.com

こんな感じのissueも立っていますが解決されていません。

で、どうしたもんかなと先ほどのHTMLファイルとにらめっこしていると、First CellにはあってFirst-sub Cellにはないclassタグを見つけました。
それが <div class="text_cell_render border-box-sizing rendered_html"> です。
toc2.js(ToCスクリプトファイル、インストール後はsite-packages>jupyter_contrib_nbextensions>nbextentions>toc2>toc2.js的なところにあった)を見ると、

all_headers = $('.text_cell_render').find('[id]:header:not(:has(.tocSkip))');

       ~中略~

        //loop over all headers
        all_headers.each(function(i, h) {
            // remove pre-existing number
            $(h).children('.toc-item-num').remove();

            var level = parseInt(h.tagName.slice(1), 10) - min_lvl + 1;
            // skip below threshold, or h1 ruled out by cfg.skip_h1_title
            if (level < 1 || level > cfg.threshold) {
                return;
            }
            h = $(h);
            // numbered heading labels
            var num_str = incr_lbl(lbl_ary, level - 1).join('.');
            if (cfg.number_sections) {
                $('<span>')
                    .text(num_str + '\u00a0\u00a0')
                    .addClass('toc-item-num')
                    .prependTo(h);
            }

            ~中略~

            // Create toc entry, append <li> tag to the current <ol>.
            ul.append(
                $('<li>').append(
                    $('<span>').append(
                        make_link(h, toc_mod_id))));
        });

のようになっているので、どうもtext_cell_renderが怪しいのではないかと。

ということで、先ほどMarkdownを出力していたセルを

from IPython.display import HTML, Markdown
display(HTML(f'<div class="text_cell_render"><h2 id="First-sub-Cell">First-sub Cell<a class="anchor-link" href="#First-sub-Cell">&#182;</a></h2></div>'))

に変えてみます。半角スペースはidやhref上ではハイフンに置き換えます。
そして、先ほどと同様にconvertすると

f:id:retarfi:20211104153538p:plain

このように、正しく(こちらの想定通りに)表示することができるようになりました。
正しい修正方法ではない気しかしないですが、とりあえず使えるようになりました。