こんにちは。でんすけ(@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’,関数名)
で、記事表示時に実行する関数を呼び出せるので
その関数内で目次生成すればよいです。
それではまたー。








コメント
コメントはありません。