markdownのパーサにkramdownを使っていたのだけど、markdownファイルに含まれる生のHTMLコードの扱いに失敗することがあるので、redcarpetを使うことに
したのだけど、redcarpetはredcarpetでtoc生成に課題が出てきたので、自前のfilterを書いた。
目次
redcarpetのtocオプションの課題
で、Nanoc4にデフォルトで含まれているredcarpetのフィルタで、toc生成オプションを有効にしてみたのだけど、
- tocの挿入位置が、bodyの先頭固定
- Filterのソース(redcarpet.rb)を見たら、HTML生成後にTOCを挿入する形になっていたのでぶっちゃけ調整しにくい
- tocのHTMLコード(ul,liタグ)にclassもidも付いていない
- CSSでデザインをいじりづらい
- 出力コードを変えるには、オリジナルのredcarpetのオプションを調べて、ガッツリfilterを書き換える必要あり
- markdownにHTMLコードでベタにhタグを書いていると拾ってくれない?
- 原因を調査する途中でもう投げたくなった
解決方法
HTMLコード(テンプレートファイル、もしくは、markdownファイル)中、{:toc} と書いた位置にtocを自動生成するfilterを書いた。
カスタムタグの定義ならredcarpetを拡張する方法もあるのだけど、今回は、markdownをパースしてさらにテンプレートを適用してHTMLに変換した後の状態に対して、tocを挿入したかったので、NanocのFilterとして実装する方法をとった。
書いたコード
jqueryを使ったjavascripのサンプルコードを元に、Nokogiriを用いて同じように書いてみた。
目次にする要素は、h2~h4に決め打ち。case文の分岐を書き換えれば変更可能。
class TocGenerator < Nanoc::Filter require 'Nokogiri' def run(content, params = {}) createToc(content) end def createToc(content) id_count = 1 toc_text = '' currentlevel = 0 doc = Nokogiri::HTML.parse(content) doc.css('div.sc-content.c-container').css('h2, h3, h4').each do |h| h["id"] = 'chapter_' + id_count.to_s id_count += 1 case h.name when 'h2' then level = 1 when 'h3' then level = 2 when 'h4' then level = 3 else level = 0 end while currentlevel < level do toc_text += '<ul class="chapter">' currentlevel += 1 end while currentlevel > level do toc_text += '</ul>' currentlevel -= 1 end toc_text += '<li><a href="#' + h['id'] + '">' + h.content + "</a></li>\n"; end while currentlevel > 0 do toc_text += '</ul>' currentlevel -= 1 end toc_text = '<div id=toc><span id=toctitle>目次</span>' + toc_text + '</div>' newcontent = doc.to_html newcontent.gsub(/{:toc}/, toc_text) end end Nanoc::Filter.register "TocGenerator", :tocGenerator
RulesファイルでHTMLファイルに変換後に適用させて完成。(↓例)
compile '/**/*.md' do filter :erb filter :redcarpet, options: {tables: true} layout 'default.*' filter :tocGenerator end