JekyllでH2見出しからサブナビゲーションを自動生成する方法

Jekyllを使ってドキュメントサイトを再構築しています。ドキュメントページはかなり大きくなりがちなため、トップレベルのナビゲーションに加えて、何らかのサブナビゲーションが必要になります。
この記事では、投稿やページ内の見出しからサブナビゲーションリンクを自動生成できる、シンプルなJekyllプラグインの作り方を解説します。
概要
このプロジェクトは、以下のタスクに分解して考えることができます。
- サイト上のすべてのページに対して実行される、Jekyllジェネレーターを作成する。
- 見出し情報を抽出できるよう、ジェネレーターにページの事前レンダリング(プリレンダリング)の方法を実装する。
- nokogiriを使ってページのHTMLを解析し、対象となる見出しとコンテンツを抽出する。
- サブナビゲーションをレンダリングする。
以下の例では、すべてのサブナビリンクはアンカーリンクになっています。このアプローチを機能させるには、マークダウンプロセッサが見出しにIDを付与するように設定しておく必要があります。with_toc_dataオプションを有効にしたRedCarpetが、この用途にぴったりです。
基本的なJekyllジェネレーター
Jekyll向けのプラグインにはいくつかの種類がありますが、ここでは「ジェネレーター」を作成します。
ジェネレーターとは、Jekyll::Generatorを継承し、generateというメソッドを持つシンプルなクラスのことです。
ジェネレーターは、JekyllがすべてのMarkdownファイルを読み込んだ後、それらがHTMLに変換される前に実行されます。generateメソッドにはsiteオブジェクトが渡され、このオブジェクトを通じてサイト内のすべてのページ・投稿・その他のリソースにアクセスできます。
以下の例では、全ページをループ処理してタイトルを出力するだけのジェネレーターを作成しています。
class MySubnavGenerator < Jekyll::Generator
def generate(site)
site.pages.each do |page|
puts page.data["title"]
end
end
end
さらに、ジェネレーターの中ではページやサイトのデータ(front-matterから読み込まれたデータや、サイト設定ファイルのデータ)を書き換えることも可能です。
page.data["title"] += " - modified!"
site.data["tagline"]
MarkdownをHTMLへ事前レンダリングする
Markdownドキュメントから見出しを抽出したい場合、最も手軽なのは、一度MarkdownをHTMLに変換してから、nokogiriのようなツールでそのHTMLを解析する方法です。
「ちょっと汚いやり方だな」と感じるかもしれません。ええ、確かに。パフォーマンス面でも遅くなるでしょう?間違いなく遅いです。しかし、Jekyllは静的サイトジェネレーターなので、リアルタイム性能を気にする必要はありません。というわけで、目をつぶってこのやり方を採用しましょう。
ここでは、Jekyllに組み込まれているMarkdownラッパーを使って、すべてのMarkdownページをHTMLに変換します。
class MySubnavGenerator < Jekyll::Generator
def generate(site)
parser = Jekyll::Converters::Markdown.new(site.config)
site.pages.each do |page|
if page.ext == ".md"
html = parser.convert(page['content'])
# ここでhtmlに対して何か処理を行う
end
end
end
end
見出しを抽出する
新しいドキュメントサイトでは、「すべてのH2タグに対応するサブナビゲーションリンクを用意する」という方針にしました。そこで、nokogiriを使って各ページのHTMLを解析し、ページからH2タグをすべて取り出します。
まずは試しに、H2のテキストとIDを画面に出力してみましょう。
require "nokogiri"
class MySubnavGenerator < Jekyll::Generator
def generate(site)
parser = Jekyll::Converters::Markdown.new(site.config)
site.pages.each do |page|
if page.ext == ".md"
doc = Nokogiri::HTML(parser.convert(page['content']))
doc.css('h2').each do |heading|
puts "#{ heading.text }: #{ heading['id'] }"
end
end
end
end
end
サブナビゲーションメニューを作成する
見出しのテキストとIDが取得できたら、サブナビゲーションリンクのリストを作成できます。
このリンクのリストは、ページ自体のデータ属性として保存します。そうすることで、ページテンプレート側からリンクを参照できるようになります。
require "nokogiri"
class MySubnavGenerator < Jekyll::Generator
def generate(site)
parser = Jekyll::Converters::Markdown.new(site.config)
site.pages.each do |page|
if page.ext == ".md"
doc = Nokogiri::HTML(parser.convert(page['content']))
page.data["subnav"] = []
doc.css('h2').each do |heading|
page.data["subnav"] << { "title" => heading.text, "url" => [page.url, heading['id']].join("#") }
end
end
end
end
end
あとは、テンプレート側でsubnavをループ処理すれば、各リンクを表示できます。
{% for item in page.subnav %}
<a href="{{ item.url }}">{{ item.title }}</a>
{% endfor %}
トラブルシューティング
繰り返しになりますが、この仕組み全体は「マークダウンプロセッサが各見出しに一意のIDを付与すること」に依存しています。参考までに、筆者の_config.ymlにおけるmarkdown設定を載せておきます。
# Redcarpet Markdownレンダラーを使用
markdown: redcarpet
redcarpet:
extensions: [
'no_intra_emphasis',
'fenced_code_blocks',
'autolink',
'strikethrough',
'superscript',
'with_toc_data',
'tables',
'hardwrap'
]
次回予告
上記のアプローチは、サブナビゲーションが1階層で足りている場合には十分に機能します。しかし、より多くの階層が必要になったらどうでしょう?例えば、H2の「内側」にあるすべてのH3タグから、さらに下位のサブサブナビゲーションリンクをメニューに生成したいケースです。
これについては、今後のブログ記事で詳しく解説する予定です。お楽しみに!
-
WebサイトからExcelへデータを自動抽出する方法|「Webから」機能の使い方を徹底解説
WebページからExcelワークシートへのデータの自動抽出(収集・更新)は、業務内容によっては欠かせない作業です。実はExcelには、Webページから直接データを取得できる機能が標準搭載されており、データ分析業務でExcelを活用する方にとって最も利用頻度の高い機能の一つとなっています。例えば金融アナリストであれば、分析のために毎日の株価をWebサイトからExcelブックへ取り込みたい場面があるでしょう。本記事では、WebサイトからExcelへデータを自動抽出する方法を、初心者にもわかる手順で丁寧に解説します。WebサイトからExcelへデータを自動抽出する基本手順ここでは、Excelの「デー
-
【Windows 10】Skypeの自動起動を無効にする方法をバージョン別に解説
Windows PCを起動するたびにSkypeが自動的に立ち上がり、そのたびに少しイラッと感じている方もいるのではないでしょうか。ただ、この自動起動には目的があります。PCを離れている間に受け取った通話、メッセージ、共有ファイルなどのやり取りを見逃さないようにするためです。とはいえ、不要だと感じるのであれば無効化して問題ありません。この記事では、Windows 10でSkypeの自動起動を停止する具体的な方法を詳しく解説します。 Skypeの設定を変更すると、メッセージや不在着信を確認する際には手動でSkypeを開く必要があります。ただし、他のアプリと同じように起動したままにしておけば、これま