PHPで原稿の文字数・行数を数える

(1/1)
Twitterのつぶやきは140文字以下、ケータイ小説の1ページは15文字×15行程度――原稿を扱う場合、その文字数・行数をあらかじめ知っておくことは大切だ。
そこで今回は、PHPを使って原稿の文字数・行数を数えるプログラムを作ってみることにする。シフトJIS、EUC-JP、Unicodeによってバイト数と文字数の関係が異なってくるので注意が必要だ。

(2021年7月10日)PHP8対応,リファラ・チェック追加,大幅改訂

目次

サンプル・プログラムの実行例

PHPで原稿の文字数・行数を数える

サンプル・プログラム

解説:文字数・行数のカウント

0037: //文字数カウント関係
0038: define('CHARS_PER_LINE', 15);       //1行あたり文字数(iPhone換算)
0039: define('LINES_PER_PAGE', 15);       //1ページあたり行数(iPhone換算)
0040: 

0167: /**
0168:  * テキストの文字数・行数をカウント
0169:  *    ・半角・全角ともに1文字としてカウントする
0170:  *    ・行頭・行末の半角空白文字は文字数としてカウントしない
0171:  *    ・空行は1行とカウントする
0172:  * @param   string $sourテキスト
0173:  * @return  array(文字数,行数,ページ数)
0174: */
0175: function countText($sour) {
0176:     $lines = 0;
0177:     $chars = 0;
0178:     $arr = preg_split("/[\n]/ui", $sour);
0179:     foreach ($arr as $ln=>$str) {
0180:         $str = trim($str);                       //行頭・行末の半角空白を除く
0181:         if ($str == '')        $lines++;          //空行
0182:         $n = mb_strlen($str);                    //文字数
0183:         $chars += $n;
0184:         $lines += ceil($n / CHARS_PER_LINE);  //行数
0185:     }
0186:     $pages = ceil($lines / LINES_PER_PAGE);      //ページ数
0187: 
0188:     return array($chars$lines$pages);
0189: }

あらかじめ、1行あたり文字数を定数 CHARS_PER_LINE に、1ページあたり行数を定数 LINES_PER_PAGE にセットしておく。
ここではiPhone換算で、各々15を代入してある。

文字数・行数のカウントはユーザー定義関数 countText で行う。
入力されたテキストを関数  preg_split  で行ごとに分解し、関数  trim  で行頭・行末の半角空白文字を削除する。これは改行コードを省く作用もある。
ちなみに、PHP5.3で関数 split は廃止されているので、preg_splitを使うのが無難だ。
そして、各々の行に関数  mb_strlen  を適用して文字数をカウントする。なぜ関数  strlen  を用いないかは後述する。
なお、空行も1行としてカウントしている。

知りたい値は文字数、行数、ページ数の3つあるので、関数  array  を使って配列の形で返している。

文字数のカウントについて

原稿がシフトJISやEUC-JPで作成されている場合――半角文字は1バイト、全角文字は2バイトである。
もし、文字数を組み込み関数  strlen  でカウントするとしたら、全角文字の場合だけ2で割れば良い。

ところが、Unicodeや、Webコンテンツの標準となりつつあるutf-8の場合、単純に2で割ればいいというわけにはいかない。
試しに次のプログラムを実行してみてほしい。(サンプル・プログラムの圧縮ファイルに同梱されている)

関数 strlen と文字数

0001: <?php
0002: define('INTERNAL_ENCODING', 'utf-8');
0003: $encode = INTERNAL_ENCODING;
0004: echo <<< EOD
0005: <html lang="ja">
0006: <head>
0007: <meta http-equiv="Content-Type" content="text/html; charset={$encode}" />
0008: </head>
0009: <body>
0010: <pre style="font-size:xx-large;">
0011: EOD;
0012: 
0013: $s1 = "A";
0014: $s2 = "α";
0015: $s3 = "";
0016: $s4 = "&#x2074F";
0017: printf("%s => strlen = %d<br />\n",  $s1strlen($s1));
0018: printf("%s => strlen = %d<br />\n",  $s2strlen($s2));
0019: printf("%s => strlen = %d<br />\n",  $s3strlen($s3));
0020: printf("%s => strlen = %d<br />\n",  $s4strlen($s4));
0021: 
0022: echo <<< EOD
0023: </pre>
0024: </body>
0025: </html>
0026: 
0027: EOD;
0028: ?>

strlen関数による文字数カウント
実際にutf-8で記された文字に関数  strlen  を適用するという単純なプログラムと、その実行結果。
半角文字 "A" は1バイト、全角文字 "α" は2バイト――ここまではいい。
ところが、漢字 "" は3バイト、その異体字である "𠝏" (Unicode: 0x2074F)に至っては4バイトにもなっている。

まず留意すべきことは、Unicodeが 2 バイトというのは誤解ということだ。なぜ誤解が生まれたかについては『ユニコード戦記』200ページに詳しい。
現時点で、Unicodeは最大4バイトまでが定義されている。これをビット・ストリームで表現するutf-8は、最大8バイトになる。つまり、「1文字=8バイト」という文字が存在するということだ。
また、名前は似ているが、utf-16は16ビット(2バイト)固定、utf-32は32ビット(4バイト)固定である点にも注意してほしい。

こうした背景を知っておくと、さきほどの関数  strlen  の結果が理解できる。
大まかにいうと、utf-8は半角英数字は7ビット(1バイト)、その他のヨーロッパ文字は11~16ビット(2バイト)、かな漢字など欧米圏外の文字は21ビット(3バイト)、使われる頻度が少ない文字や古代文字などは26~32ビット(4バイト)といった形で割り当てられている。
このように1文字のビット数(バイト数)が可変になることをマルチバイト文字と呼んでいる。

では、マルチバイト文字の1文字を“1文字”としてカウントするにはどうしたらいいか――組み込み関数  mb_strlen  の出番である。

関数 mb_strlen と文字数

0001: <?php
0002: define('INTERNAL_ENCODING', 'utf-8');
0003: mb_internal_encoding(INTERNAL_ENCODING);
0004: $encode = INTERNAL_ENCODING;
0005: echo <<< EOD
0006: <html lang="ja">
0007: <head>
0008: <meta http-equiv="Content-Type" content="text/html; charset={$encode}" />
0009: </head>
0010: <body>
0011: <pre style="font-size:xx-large;">
0012: EOD;
0013: 
0014: $s1 = "A";
0015: $s2 = "α";
0016: $s3 = "";
0017: $s4 = "&#x2074F";
0018: printf("%s => mb_strlen = %d<br />\n",  $s1mb_strlen($s1));
0019: printf("%s => mb_strlen = %d<br />\n",  $s2mb_strlen($s2));
0020: printf("%s => mb_strlen = %d<br />\n",  $s3mb_strlen($s3));
0021: printf("%s => mb_strlen = %d<br />\n",  $s4mb_strlen($s4));
0022: 
0023: echo <<< EOD
0024: </pre>
0025: </body>
0026: </html>
0027: 
0028: EOD;
0029: ?>

mb_strlen関数による文字数カウント
関数  mb_strlen  を使えば、どんなマルチバイト文字に対しても正確に文字数を返してくれる。もちろん、シフトJISやEUC-JPの場合にも1文字として値を返してくれる。
ただし、事前に組み込み関数  mb_internal_encoding  を使って文字エンコード方式を明示しておく必要はある。

ともかく、文字数をカウントする場合には関数 mb_strlen を使う と覚えておくとよいだろう。
また、PHPでマルチバイト文字(列)を扱う場合、エンコードはutf-8にしてmb_系およびpreg_系の関数を使うのが定石である。ereg 系の関数はPHP5.3で廃止されたので使わない方がいい。

余談:twitterはなぜ140文字なのか

twitterの共同創業者ビズ・ストーンさんはこう語っている。
ツイッターがSMS(ショートメッセージサービス。国際標準規格となっている)でも活用できるようにしたかったのです。SMSはヨーロッパをはじめ世界中に広まっています。 SMSの設けている字数制限は、世界共通で160文字です。メッセージの前にユーザーネームのスペースを確保したいと考え、140文字を私たちは基本設定としたのです。(121ページ)


ちょっと感動した。
すでに見てきたように、半角文字を1バイト(0.5文字)、全角文字を2バイト(1文字)と数えるのは日本の技術者のローカルルールだ。
twitterのように、半角も全角も1文字としてカウントするシステムが、これからのグローバル・スタンダードになるのだと思う。

参考サイト

参考書籍

表紙 ユニコード戦記
著者 小林龍生
出版社 東京電機大学出版局
サイズ 単行本
発売日 2011年06月
価格 2,970円(税込)
ISBN 9784501549701
ドラえもん担当編集者が、数奇な運命でICT基盤を支える国際符号化文字集合の責任者になるまでの波瀾万丈。笑いと涙と友情を通して浮かび上がる情報通信技術標準戦争の現在。
 
表紙 つながる力 ツイッターは「つながり」の何を変えるのか?
著者 勝間和代/広瀬香美
出版社 ディスカヴァー・トゥエンティワン
サイズ 単行本
発売日 2009年12月
価格 1,430円(税込)
ISBN 9784887597747
「ツイッターって何?」「ミクシィや2ちゃんねるとどこがちがう?」「皆どうして夢中になるの?」ネット上を震憾させた凸凹コンビが、そんな疑問に全てお答えします。ビズ・ストーン(twitter共同創業者)×勝間和代、スペシャル対談&本社訪問レポートを特別収録。
 
(この項おわり)
header