こんにちは。でんすけ(@notgeek_densuke)です。
皆さん、ブログに「目次」付けてますか?
あってもなくてもいいんですけど、
あった方が、読み手のこと考えたブログだな、という感じがしますよね。
でも、すで記事もたくさん書いてきたし、
いまから目次を作るのは大変なんだよなー、
という方もいらっしゃるはず。
そんなときのために。
目次を作るスクリプトを書いてみました。
スポンサーリンク
目次作成スクリプトの仕様
まず、前提として
・すべての記事に目次を追加する
・見出しタグ(h2)へのリンクを作成
・リンク先URLは、h2のidにする。(<a href=”#h2のid”>)
・見出しに「id」がない場合は「autoid_1」「autoid_2」のように連番のidを自動付与
・「一番最初の見出しタグ」の前に目次を設置
という仕様にすることにします。
目次作成スクリプトの実装
functions.phpに
こんな感じのコードを記述。
function insert_table_of_contents( $the_content ){ if(is_single()) { //投稿ページの場合 $tag = '/^<h2.*?>(.+?)<\/h2>$/im'; //見出しタグの検索パターン if(preg_match_all($tag, $the_content, $tags)) { //本文中に見出しタグが含まれていれば $idpattern = '/id *\= *["\'](.+?)["\']/i'; //見出しタグにidが定義されているか検索するパターン $table_of_contents = '<div class="table_of_contents"><p class="title"><目次></p><ul>'; $idnum = 1; $nest = 0; for($i = 0 ; $i < count($tags[0]) ; $i++){ if( ! preg_match_all($idpattern, $tags[0][$i], $idstr) ){ //見出しタグにidが定義されていない場合、「autoid_1」のようなidを自動設定 $idstr[1][0] = 'autoid_'.$idnum++; $the_content = preg_replace( "/".str_replace('/', '\/' ,$tags[0][$i])."/", preg_replace('/(^<h2)/i', '${1} id="' . $idstr[1][0] . '" ' , $tags[0][$i]), $the_content,1); } //見出しへのリンクを目次に追加。<li>でリスト形式に。 $table_of_contents .= '<li><a href="#' . $idstr[1][0] . '">' . $tags[1][$i] .'</a>'; } $table_of_contents .= '</ul></div>'; //目次の各タグを閉じる if($tags[0][0]){ //作った目次を、1番目の見出しタグの直前に追加 $the_content = preg_replace('/(^<h[2-6].*?>.+?<\/h[2-6]>$)/im', $table_of_contents.'${1}', $the_content,1); } } } return $the_content; } // 記事表示時に「insert_table_of_contents()」を実行する add_filter('the_content','insert_table_of_contents');
ざっくりした処理の流れはコメントの通りですが、
・記事本文から見出しタグ(h2)を探す
・h2にidがなかったら自動付与
・h2へのリンクを、目次に追加
・それを繰り返して目次を作ったら、最初の見出しの直前に目次を設置
という感じです。
ハイライトした14行目が異様にややこしくなったんですが、
「全く同じタイトルを付けた見出し」
というのを回避するためにこんな書き方になりました。
あんまりないケースだとは思うのですが、一応。
で、あとCSSも必要。
CSSは、ざっくりだけどこんな感じ。
div.table_of_contents{ border:solid 1px #000000; background-color:#f3f3f3; max-width:300px; margin:20px auto; } div.table_of_contents p.title{ padding:10px 0; text-align:center; } div.table_of_contents p, div.table_of_contents ul, div.table_of_contents li { margin:0; } div.table_of_contents ul li ul { margin-left: -10px; }
これらをそれぞれ、
functions.phpと
style.cssに書けば
こんな見た目の目次が各記事に表示されるようになるはず。
デモなので、リンク先URLは#にしてます。
h2以外の見出しも目次に
で、先ほどのコードだと、h2だけを対象にして目次にしていましたが、
その他の見出し<h3>~<h6>も目次に入れるとすると、
こんな感じになります。
変更点は、ハイライトしたところ。
function insert_table_of_contents( $the_content ){ if(is_single()) { //投稿ページの場合 $tag = '/^<h[2-6].*?>(.+?)<\/h[2-6]>$/im'; //見出しタグの検索パターン if(preg_match_all($tag, $the_content, $tags)) { //本文中に見出しタグが含まれていれば $idpattern = '/id *\= *["\'](.+?)["\']/i'; //見出しタグにidが定義されているか検索するパターン $table_of_contents = '<div class="table_of_contents"><p class="title"><目次></p><ul>'; $idnum = 1; $nest = 0; for($i = 0 ; $i < count($tags[0]) ; $i++){ if( ! preg_match_all($idpattern, $tags[0][$i], $idstr) ){ //見出しタグにidが定義されていない場合、「autoid_1」のようなidを自動設定 $idstr[1][0] = 'autoid_'.$idnum++; $the_content = preg_replace( "/".str_replace('/', '\/' ,$tags[0][$i])."/", preg_replace('/(^<h[2-6])/i', '${1} id="' . $idstr[1][0] . '" ' , $tags[0][$i]), $the_content,1); } //見出しへのリンクを目次に追加。<li>でリスト形式に。 $table_of_contents .= '<li><a href="#' . $idstr[1][0] . '">' . $tags[1][$i] .'</a>'; } $table_of_contents .= '</ul></div>'; //目次の各タグを閉じる if($tags[0][0]){ //作った目次を、1番目の見出しタグの直前に追加 $the_content = preg_replace('/(^<h[2-6].*?>.+?<\/h[2-6]>$)/im', $table_of_contents.'${1}', $the_content,1); } } } return $the_content; } add_filter('the_content','insert_table_of_contents');
正規表現を変えただけです。
例えば、h4までしか要らない、という場合は、
$tag = '/^<h[2-4].*?>(.+?)<\/h[2-4]>$/im'; //見出しタグの検索パターン
みたいに変えられます。
これで、こんな感じになります。
見出しタグのネストを目次に反映
見出しタグのネスト構造を意識してブログを書かれている場合は
目次もネストしたいかもしれない。
そんなことも思ったので、
見出しタグのネスト構造を反映した目次も作ってみました。
function insert_table_of_contents( $the_content ){ if(is_single()) { //投稿ページの場合 $tag = '/^<h[2-6].*?>(.+?)<\/h[2-6]>$/im'; //見出しタグの検索パターン if(preg_match_all($tag, $the_content, $tags)) { //本文中に見出しタグが含まれていれば $idpattern = '/id *\= *["\'](.+?)["\']/i'; //見出しタグにidが定義されているか検索するパターン $table_of_contents = '<div class="table_of_contents"><p class="title"><目次></p><ul>'; $idnum = 1; $nest = 0; $nestTag = array(); for($i = 0 ; $i < count($tags[0]) ; $i++){ if( ! preg_match_all($idpattern, $tags[0][$i], $idstr) ){ //見出しタグにidが定義されていない場合、「autoid_1」のようなidを自動設定 $idstr[1][0] = 'autoid_'.$idnum++; $the_content = preg_replace( "/".str_replace('/', '\/' ,$tags[0][$i])."/", preg_replace('/(^<h[2-6])/i', '${1} id="' . $idstr[1][0] . '" ' , $tags[0][$i]), $the_content,1); } //見出しへのリンクを目次に追加。<li>でリスト形式に。 $table_of_contents .= '<li><a href="#' . $idstr[1][0] . '">' . $tags[1][$i] .'</a>'; //見出しのネスト対応 if($i+1 >= count($tags[0])){ $table_of_contents .= '</li>'; } else{ preg_match_all('/^<h([2-6])/i' , $tags[0][$i] , $match1); preg_match_all('/^<h([2-6])/i' , $tags[0][$i+1], $match2); if($match1[1][0] < $match2[1][0]){ $table_of_contents .= '<ul>'; $nestTag[] = $match1[1][0]; $nest++; } else if($match1[1][0] == $match2[1][0]){ $table_of_contents .= '</li>'; } else{ while( count($nestTag) > 0 && $nestTag[count($nestTag)-1] >= $match2[1][0]){ $table_of_contents .= '</li></ul>'; array_splice($nestTag,count($nestTag)-1,1); $nest--; } $table_of_contents .= '</li>'; } } } //入れ子のまま終わった時<ul>を閉じる for(; $nest > 0 ; $nest--){ $table_of_contents .= '</ul></li>'; } $table_of_contents .= '</ul></div>'; //目次の各タグを閉じる if($tags[0][0]){ //作った目次を、1番目の見出しタグの直前に追加 $the_content = preg_replace('/(^<h[2-6].*?>.+?<\/h[2-6]>$)/im', $table_of_contents.'${1}', $the_content,1); } } } return $the_content; } add_filter('the_content','insert_table_of_contents');
なんか結構複雑になった・・・
もうちょっとカッコよく書けたのかなぁ。
一応動いてるっぽいのでこれで。
動作例はこんな感じ。
まとめ
ということで、ブログに目次を自動追加してみよう、というときのレシピ例でした。
add_filter(‘the_content’,関数名)
で、記事表示時に実行する関数を呼び出せるので
その関数内で目次生成すればよいです。
それではまたー。
コメント
コメントはありません。