投稿内容(記事本文)の取得
目次を提示したいときなどに記事内のタグを取得したいときがあります。プラグインを使う手もありますが、自力でもそれほど難しくはないです。今回は記事内のhタグを取得する方法を紹介したいです。
wordpress
での投稿内容は the_content()
で取得できるが、今回は投稿内容を加工する必要がありますので、投稿情報が格納されるグローバル変数$post
から投稿内容(記事本文)を取得することになります。
投稿内容は$post
のpost_content
というメンバ変数に格納されています。ループ内で以下の記述で取得できます。
$content = $post->post_content;
この記事本文には<p>タグ
や<img>タグ
など、挿入したタグもそのままの形で入っています。もちろんタグも入っています。
マッチング
preg_match_allを使う
$post->post_content
中から、タグを選別するためにはPHPの関数であるpreg_match_all
を使う。これは文字列の中から任意の検索条件にヒットする文字列をすべて取得する関数です。具体的には以下の様に記述することになります。
preg_match_all('/<h[1-6]>.+<\/h[1-6]>/u', $post->post_content, $matches);
/<h[1-6]>.+<\/h[1-6]>/u
は後述するがタグを取得する為の検索条件。これを用いて$post->post_content
からタグを取得していきます。取得したタグは$matches
に格納されていきます。
正規表現
<h>タグを検索するための正規表現
任意の検索条件とは「正規表現」と呼ばれる方法で条件指定を行う。先ほどの/<h[1-6]>.+<\/h[1-6]>/u
が正規表現で記述された検索条件です。
正規表現はなかなかに難しく、すべてを語り倒せるほどの知識は@MINOにはないので、とりあえず今回の正規表現の意味を説明したいと思います。
<h[1-6]>
は<h1><h2><h3><h4><h5><h6>
にヒットします。 [1-6]
は123456
のどれか一文字という意味となります。.(ドット)
は改行を除く任意の一文字、+(プラス)
は一回以上の繰り返しを意味します。
なので.+は任意の一文字以上の文字列を意味することになります。つまりタグで囲まれた見出し本文に当たります。
<\/h[1-6]>
は終了タグ</h1></h2></h3></h4></h5></h6>
にヒットします。 \(バックスラッシュ)
があるがこれは/(スラッシュ)
をエスケープする為の記号となります。
前後に/(スラッシュ)
がありますが、これはデリミタと呼ばれるもので、ここから正規表現が始まり、ここで終わりですよという意味になります。末尾のu
はマルチバイト(UTF-8)対応させるために必要になります。
この正規表現で投稿内容(記事本文)の<h>タグ
すべてを取得できます。
idやクラスを用いた場合
@MINOは記事内で<h>タグ
にid
やクラス
を使用はせず、CSS
で親子関係でスタイルを指定していますが、id
やクラス
を必要としている方もいるでしょう。
タグにid
やクラス
を用いる際はこの正規表現ではヒットさせる事はできないです。
/<h[1-6]\s.+>.+<\/h[1-6]>/u
のように開始タグに.+
を付け加えると
<h1 id="hoge">これはh1です</h1>
<h1 class="hoge">これはh1です</h1>
などにヒットさせることができます。
タグになんらかの属性を用いる場合に参考にしてほしいです。
実際に<h>タグ
を取得する
関数の定義
さて、基本的な内容を説明したうえで具体例に入りたいと思います。テンプレートファイルに直接処理を書き込んでも構わないですが、やはりfunctions.php
に関数として定義するのが良いかと思います。<h>タグ
を取得する関数は以下の様になります。
//functions.phpに定義する関数
function get_index() {
//グローバル変数を使う為の宣言
global $post;
//マッチングで<h>タグを取得する
preg_match_all('/<h[1-6]>.+<\/h[1-6]>/u', $post->post_content, $matches);
//取得した<h>タグの個数をカウント
$matches_count = count($matches[0]);
if(empty($matches)){
//<h>タグがない場合の出力
echo '<span>Sorry no index</span>';
}else{
//<h>タグが存在する場合に<h>タグを出力
for ($i = 0; $i < $matches_count; $i++){
echo $matches[0][$i];
}
}
}
preg_match_all
を使って記事本文から<h>タグ
をすべて取得し、取得した個数分出力するという処理です。
テンプレートファイルで使用する
ここで定義した関数をテンプレートファイルで使用することで<h>タグ
が出力されます。 以下はsingle.php
での使用例です。
<?php get_header(); ?>
<div id="container">
<?php if (have_posts()) : the_post();?>
<h1 class="title_h1">
<?php echo the_title(); ?>
</h1>
<div id="index">
<?php get_index(); //ここで<h>を出力する?>
</div>
<div id="content">
<?php echo the_content(); ?>
</div>
<?php endif; ?>
<?php get_sidebar(); ?>
</div>
<?php get_footer(); ?>
例えば<h2>タグ
だけ取得したい場合は正規表現を/ <h2\s.+>.+<\/h2>/uとして<h2>
タグのみヒットするようにすることも可能です。
取得したタグを加工する
ナンバリング
抽出した<h>タグ
を目次として使いたいとき、数字を付けたい場合があります。
元 → <h1>これはh1です</h1>
ナンバリング → <h1>1.これはh1です</h1>
もとの<h>タグ
内でナンバリングする方法もあるだろうが、目次用途の時だけナンバリングしたい場合は具合が悪いです。関数に少し処理を付け加えてナンバリングを実現してみます。
置き換えでナンバリングを実現
加工にはpreg_replace
を使いたいです。正規表現でヒットした文字列を指定した文字列に置き換える関数です。いくつかナンバリングの方法は考えられると思いますが、今回は開始タグを置き換える方法で実現してみます。 以下の様なイメージです。
元 → <h1>
置き換え後 → <h1>1.
ナンバリングで階層を表現
ただやみくもにナンバリングすると、<h1>
にも<h2>
にも連番で数字が付いてしまいます。各タグごとのナンバリングはもちろんのこと、上位タグに切り替わった際、数字をリセットする必要があります。その為、関数では<h1>
から<h6>
までの場合に分けて処理をする形にしています。
各if文内でカウント用の変数を1にリセットすることで階層を表現しています。たとえば<h2>
の下に<h3>
がある場合、それは<h2>
に対しての小見出しとなると思います。
その為、新しい<h2>
の下の<h3>
とナンバリングが続いているのはおかしいです。他のタグも同様です。その為上位のタグが出現する度に下位のタグのナンバリングをリセットする必要があります。
<h1>なら2~6、<h2>なら3~6
をリセットすれば階層を考慮したナンバリングをすることが可能になります。
ナンバリング機能を付けた関数の定義
コードはすこし長くなりますが、ナンバリング機能を付けた関数を定義してみましましました。入れ子が多重なのであまりいい例ではないかもしれないですが、一応用は足す関数になっています。
//functions.phpに定義する関数ナンバリングあり
function get_index_with_number() {
//グローバル変数を使う為の宣言
global $post;
//ナンバリング用のカウント
$h1_count = 1;
$h2_count = 1;
$h3_count = 1;
$h4_count = 1;
$h5_count = 1;
$h6_count = 1;
//マッチングで<h>タグを取得する
preg_match_all('/<h[1-6]>.+<\/h[1-6]>/u', $post->post_content, $matches);
//取得した<h>タグの個数をカウント
$matches_count = count($matches[0]);
if(empty($matches)){
//<h>タグがない場合の出力
echo '<span>Sorry no index</span>';
}else{
//<h>タグが存在する場合に<h>タグを出力
for ($i = 0; $i < $matches_count; $i++){
//取得したタグがどれであるかを判断するため開始タグだけ取り出す
preg_match('/<h[1-6]>/u', $matches[0][$i] , $matches2);
//<h1>タグの場合
if($matches2[0] == "<h1>"){
//開始タグを<h1>から<h1>1.のように書き換える
$number_h = preg_replace('/<h[1-6]>/u', $matches2[0] . $h1_count .".", $matches[0][$i]);
//<h1>用のカウント送り
$h1_count++;
//他のカウントをリセットする
$h2_count = 1;
$h3_count = 1;
$h4_count = 1;
$h5_count = 1;
$h6_count = 1;
echo $number_h;
//<h2>タグの場合
}else if($matches2[0] == "<h2>"){
//開始タグを<h2>から<h2>1.のように書き換える
$number_h = preg_replace('/<h[1-6]>/u', $matches2[0] . $h2_count .".", $matches[0][$i]);
//<h2>用のカウント送り
$h2_count++;
//下位のタグのナンバリングカウントをリセットする
$h3_count = 1;
$h4_count = 1;
$h5_count = 1;
$h6_count = 1;
echo $number_h;
//<h3>タグの場合
}else if($matches2[0] == "<h3>"){
//開始タグを<h3>から<h3>1.のように書き換える
$number_h = preg_replace('/<h[1-6]>/u', $matches2[0] . $h3_count .".", $matches[0][$i]);
//<h3>用のカウント送り
$h3_count++;
//下位のタグのナンバリングカウントをリセットする
$h4_count = 1;
$h5_count = 1;
$h6_count = 1;
echo $number_h;
//<h4>タグの場合
}else if($matches2[0] == "<h4>"){
//開始タグを<h4>から<h4>1.のように書き換える
$number_h = preg_replace('/<h[1-6]>/u', $matches2[0] . $h4_count .".", $matches[0][$i]);
//<h4>用のカウント送り
$h4_count++;
//下位のタグのナンバリングカウントをリセットする
$h5_count = 1;
$h6_count = 1;
echo $number_h;
//<h5>タグの場合
}else if($matches2[0] == "<h5>"){
//開始タグを<h5>から<h5>1.のように書き換える
$number_h = preg_replace('/<h[1-6]>/u', $matches2[0] . $h5_count .".", $matches[0][$i]);
//<h5>用のカウント送り
$h5_count++;
//下位のタグのナンバリングカウントをリセットする
$h6_count = 1;
echo $number_h;
//<h6>タグの場合
}else if($matches2[0] == "<h6>"){
//開始タグを<h6>から<h6>1.のように書き換える
$number_h = preg_replace('/<h[1-6]>/u', $matches2[0] . $h6_count .".", $matches[0][$i]);
//<h6>用のカウント送り
$h6_count++;
echo $number_h;
}
}
}
}
ナンバリングに限らず、なんらかの加工を試みる場合に参考にしていただけるのではないかと思います。
まとめ
本文から任意にタグを取得したりする処理はwordpress
を使っていると意外と欲しくなるときが多いです。記事の内容などを抜粋する場合、抜粋文だけでなく、見出しや画像などを抽出して用いるとコンテンツの構築に選択肢が増えるのではないかと思います。
もちろんプラグインを使う手もありますが、自分で作ってみる事でwordpress
やPHP
の理解が深まるのでお勧めです。