2016/10/18 21:30:28

wordpressで記事の文字数をカウントする

目次(クリックするとジャンプします)
  • 1:この記事は〇〇文字で〇分で読むことが出来ます
  • 2:記事の中身
  • 3:どのように文章の文字数をカウントするか
  • 4:正規表現パターンを考える
  • 4.1:タグを特定しうる文字
  • 4.2:タグ内で使われるだろう文字
  • 4.3:タグにマッチさせるパターン
  • 4.4:日本語URLの対応が出来なかった
  • 5:文字数をカウントする
  • 5.1:コード例
  • 6:補足説明
  • 6.1:マッチングにはpreg_match_all
  • 6.2:mb_strlenで文字数をカウント
  • 6.3:その他の補足
  • 7:読み終わりの目安
  • 7.1:文字数から読了時間を計算する
  • 7.2:読了時間関数
  • 8:まとめ

この記事は〇〇文字で〇分で読むことが出来ます

よくブログなどで「この記事は〇〇文字で〇分で読むことが出来ます」的な文言が入っている場合があります。

ユーザーに記事の規模を伝えることができ、よい情報だと思います。これもプラグインがいろいろあるようなので、プラグインを使った方が早いのですが、このサイトではそれを良しとせず、なるべく自力で機能を作ってみようと思います。

記事の中身

記事の文字数をカウントするにあたり、記事本文がどのような構造になっているか知らなければです。

wordpressの記事本文つまりthe_content()で取得できる情報は、エディタで書いた文章・htmlタグがそのままが入っています。

文章とhtmlタグが切り分けられていれば、すぐにでも文字数カウントができるのですが、そういう訳にはいかないのです。

どのように文章の文字数をカウントするか

ここはやはり正規表現の出番でしょう。htmlタグをマッチングさせ、その文字数を記事の総文字数から引けば、文章の文字数が判明しそうです。

最初、日本語をカウントした方が早いのではないかと思いましたが、文章中にもアルファベットは出てくるので、日本語だけカウントしても記事によっては文字数が実際とかけ離れてしまうかもしれないです。

またアルファベットを正規表現でパターンマッチングさせることは簡単ですが、htmlタグとの区別が非常に難しく思います。htmlタグであれば"<"">"が付くのでそれを基準とするとタグであることが特定がしやすいです。

以上の事から今回は下記のようにして記事文字数を取得したいと思います。

文章・htmlタグの文字数合計 - htmlタグの文字数 = 文章の文字数

正規表現パターンを考える

タグを特定しうる文字

前述したとおりhtmlタグ"<"">"で囲まれています。だから"<"で始まって">"で終わる文字列というパターンが基本になりそうです。

文章中にタグではない"<"">"で囲まれた文字列が出てくるかもしれないですが、タグとして解釈されてしまうので、"<"">"を「文章の文字」として使いたい場合はエスケープしてしかるべきでしょう。

"<"">"で囲まれた日本語の文字列があるかもしれないですが、これから作る正規表現マッチパターンは日本語のマッチングを行わないのでカウントされることはないです。

タグ内で使われるだろう文字

タグ内で使われるだろう文字は以下だとおもう。

アルファベット aからz AからZ 数字 0から9 記号 " ' = . _ # & % @ ? ! : ; - () {} [] $ | ~ ,

タグ自体はほとんどがアルファベット、もしくは数字との組み合わせなので、パターンマッチングさせるのは簡単ですが、タグの属性をマッチングさせるのはちょっと複雑です。

たとえばstyle属性を指定していれば、color:"#000000";という記述が出てくるかもしれないです。ダブルコーテーションではなくシングルコーテーションで囲む場合もあるだろうから、これだけでも':"#;と5つも記号がつかわれています。色に関して言えばrgba( 100 , 200 , 100 , 1.0)のような指定方法もありますので、,.()も必要です。

aタグなどでのURL指定はより想定範囲が広がます。 ちょっと大げさな例ですが、以下のようなURLは存在しえるでしょう。

http://hoge-hoge_hoge.jp/~hoge?a=200&b=300&c=2%de%fege%dke%

グーグルマップのURLでは@が使われているし、@MINOは既知ではないが他の記号も何らかの形で使われる可能性があります。

タグにマッチさせるパターン

以上の事を踏まえたうえでマッチパターンを考えてみましましました。

/<[a-zA-Z0-9\s=':;,{}#$%&@~<>\"\!\?\|\/\._\-\( \)\\\[\]]+>/

¥マークがたくさんあって気持ち悪いですが、記号の多くはメタ文字(機能を有する文字)なので、でエスケープする必要があります。

このパターンでタグの取得を行います。

日本語URLの対応が出来なかった

リンクに使うURLには日本語URLも存在するので本来はそれもマッチングさせる必要がありますが、@MINOの正規表現パターン構築の能力不足でそれを実現するパターンを作る事が出来ませんでしました。

例えば以下の様な正規表現を考えましました。

/<[\W\w]*>/

えらく短いパターンですが、"<"">"で挟まれているものであれば日本語が入っていてもマッチングさせる事ができます。 しかしこれには問題があります。以下のような文字列を想定してみてほしいです。

<p>あいうえお</p>

何の変哲もないタグで囲まれた日本語ですが、先ほどのマッチパターンでは<p>あいうえお</p>全部がマッチします。 なぜかというと一番最初の<と一番最後の>で囲まれているからです。

途中に入っている><も条件に入ってしまうので、本来ならマッチさせたくない日本語もマッチしてしまいます。

もし日本語URLをリンクで用いる際はエスケープすることで、文字数がカウントできない状況を回避できると思います。

文字数をカウントする

コード例

それでは実際にコードを組んでみたいと思います。以下のようなコードになりましました。

function word_counter(){
	global $post;
	$text = $post->post_content;
	preg_match_all( "/<[a-zA-Z0-9\s=':;,{}#$%&@~<>\"\!\?\|\/\._\-\( \)\\\[\]]+>/" , $text , $match );
        echo "<div>下段の記事本文にてパターンマッチしたタグ</div>";
	for( $i=0; $i < count( $match[0] ); $i++ ){
		$removal_all = $removal_all + mb_strlen( $match[0][$i] , "UTF-8" );
                echo "<div>" . htmlentities( $match[0][$i] , ENT_QUOTES ) . "</div>";
	}
	$content_text = mb_strlen( $text , "UTF-8" ) - $removal_all;
        echo "<div>--------------------</div>";
        echo "<div>全文字数" .  mb_strlen( $text , "UTF-8" ) . "字</div>";            
        echo "<div>タグに使われている文字数" .  $removal_all . "字</div>";
        echo "<div>文章の文字数" .  $content_text . "字</div>";
	return $content_text; 
}

補足説明

マッチングにはpreg_match_all

今回の関数の核となるのが、preg_match_allです。これは対象文字列からマッチパターンにマッチした文字列をすべて格納します。 上述したマッチパターンを指定して、$post->post_content;にてマッチングさせています。

これでマッチした文字列(今回であればタグに使われている文字)はマッチするごとにどんどん$matchに格納されていく。$matchは二次元配列の形になるので注意が必要です。デフォルトでは格納は以下の形になります。

Array ( [0]=>Array ( [0]=>タグ1 [1]=>タグ2 [2]=>タグ3 [3]=>タグ4 [4] =>タグ5 [5]=>タグ6))

第三引数にflagを与えることでき、配列への格納形式を変えることができるが、今回は特に必要がないので取り上げないです。もし気になる場合はPREG_PATTERN_ORDERPREG_SET_ORDERPREG_OFFSET_CAPTUREで検索して調べてみることをお勧めします。 (ちなみにデフォルトはPREG_PATTERN_ORDER)

mb_strlenで文字数をカウント

mb_strlenは文字数をカウントする関数です。似た関数にstrlenがありますが、こちらはマルチバイトに対応していないので、日本語のカウントが上手くできないです。mb_strlenはマルチバイトに対応しているので日本語を使っているならmb_strlenを使った方がよいです。

第一引数に文字数をカウントしたい文字列を指定して、第二引数に文字エンコードを指定します。 文字エンコードは指定しなくても動くが、不具合の原因になり兼ねないので、指定した方がのちのち泣かなくて済みます。

例では配列数を条件にしてfor文を回し、$removal_allにタグに使われている文字数をどんどん足しています。

こんどは$post->post_content($text)の文字数をカウントしてそこから$removal_all分を引いています。

これでタグではない文字、つまり文章本体の文字数が取得できるわけです。

その他の補足

ところどころechoで注釈文が出力されていますが、デモと同じコードだからです。実際にはreturnしている$content_textに文章文字数が入ることになるので、それだけ出力できれば用は足ります。

読み終わりの目安

文字数から読了時間を計算する

文字数を取得することができたので、読了時間も計算してみたいです。これは簡単で1分間で読める文字数で取得した文章の文字数を割ってあげればよいです。

1分間で読める文字数に関してですが、人によってスピードは違うので正解はないですが、一般的であれば大体500~600文字程度だと言われています。これを参考にしたいです。

読了時間関数

以下の様なコードで読了時間を取得してみましましました。

function read_end(){
    $ave = 600;
    $dev = get_word_count() / $ave;
    if( $dev < 1 ){
	$dev = 1;	
    }
    return round( $dev , 0 );
}

短いコードです。文章の文字数を取得するget_word_count()を基準となる600で割っています。あとは分で出力する為に、1未満になったら全部1にして、最終的にroundで小数点を丸めています。

それほど正確性を求められないものだと思うので、少しくらいの丸めには目をつぶっておきましょう。

まとめ

単純な機能ですが、ユーザーに情報を得てもらうのはとても大切なことだと思います。しかし@MINOは正規表現が苦手でいつも死にそうになります。うまくパターンを作れないからです。どこか不完全になってしまいます。できればバリデーションチェックの仕事などしたくはないです。

そういう選り好みをしているからいつまでたっても9割方無職なのでしょう。

実のところwordpressのフィルター使えばいいんじゃねえかというのもあるが黙っておきましょう。

プラグインなど使ってたまるか。