PHPでテキストにルビを振る

(1/1)
Yahoo!JAPANの「ルビ振りWebサービス(V2)」は、日本語文にルビ(ふりがな)を振るWebAPIである。このWebAPIを利用し、入力したテキストに <ruby> タグでルビを振るPHPプログラムを作る。

(2021年11月6日)入力テキスト中の改行を<br />に変換するようにした.
(2021年11月3日)漢字のみにルビを振るオプションを追加した。
(2021年10月30日)rubyタグ中の改行が半角スペースになるようなので、改行出力しないよう修正した。
(2021年10月7日)"pahooNormalizeText.php" のデバッグコードを消去。
(2021年9月26日)現行WebAPIが2022年1月末に終了することからルビ振り(V2)に変更
(2021年7月10日)PHP8対応,リファラ・チェック改良,コピー・ボタン追加

目次

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

PHPでテキストにルビを振る
プログラムを実行すると、左側のテキストボックスにサンプル・テキストが表示される。これは当サイトの「西暦1000年 - 藤原彰子が中宮に」の中の一節である。
【使い方】に示しているように、[ルビ] ボタンを押すことで右側にルビ付きテキストが表示される。

サンプル・プログラム

圧縮ファイルの内容
YahooRuby.phpサンプル・プログラム本体
pahooNormalizeText.phpテキスト正規化クラス pahooNormalizeText。
テキスト正規化クラスの使い方は「PHPで日本語テキストを正規化」を参照。include_path が通ったディレクトリに配置すること。

準備:pahooNormalizeText クラス

0012: class pahooNormalizeText {
0013:     var $items;      //検索結果格納用
0014:     var $error;      //エラーフラグ
0015:     var $errmsg; //エラーメッセージ
0016:     var $hits;       //検索ヒット件数
0017:     var $webapi; //直前に呼び出したWebAPI URL
0018: 
0019:     //Yahoo! JAPAN Webサービス アプリケーションID
0020:     //https://developers.google.com/maps/documentation/javascript/get-api-key
0021:     var $YAHOO_APPLICATION_ID = '**********************************';
0022: 
0023:     //gooラボ アプリケーションID
0024:     //https://labs.goo.ne.jp/apiregister/
0025:     var $GOOLABS_APPLICATION_ID = '***********************************';
0026: 
0027:     //MeCabの実行プログラム;各自の環境に合わせて変更のこと
0028:     var $MECAB = 'C:\Program Files (x86)\MeCab\bin\mecab.exe';
0029:     //ユーザー辞書
0030:     var $FILE_UDIC_MECAB =  'C:\Program Files (x86)\MeCab\dic\user_wiki.dic';
0031:     //特殊変換ファイル名
0032:     var $FILE_SPECIAL = 'special_table.txt';

テキスト処理に必要なプログラムは、pahooNormalizeText クラスとして別ファイルに分離した。

Yahoo! JAPAN Webサービスを利用するには Yahoo! JAPAN Webサービス アプリケーションID が必要で、その入手方法は「Yahoo!JAPAN デベロッパーネットワーク - WebAPIの登録方法」を参照されたい。

Yahoo!JAPAN ルビ振りWebサービス

ルビ振りWebサービス(V2)」は、入力パラメータとしてJSON文字列をPOSTで、User-Agentに Yahoo! JAPAN Webサービス アプリケーションID を指定し、出力結果がJSONで戻るというWebAPIである。
WebAPIのURL
URL
https://jlp.yahooapis.jp/FuriganaService/V2/furigana

入力パラメータ
フィールド名 要否 内  容
id 必須 JSON-RPC 2.0のid。値は任意で、指定した値がレスポンスのidに返る。
jsonrpc 必須 "2.0" 固定。
method 必須 "jlp.furiganaservice.furigana" 固定。
params q 必須 ルビを振る日本語テキスト。UTF-8エンコード。
grade 任意 学年
1: 小学1年生向け。漢字(注2)にふりがなを付ける。
2: 小学2年生向け。1年生で習う漢字にはふりがなを付けない。
3: 小学3年生向け。1~2年生で習う漢字にはふりがを付けない。
4: 小学4年生向け。1~3年生で習う漢字にはふりがなを付けない。
5: 小学5年生向け。1~4年生で習う漢字にはふりがなを付けない。
6: 小学6年生向け。1~5年生で習う漢字にはふりがなを付けない。
7: 中学生以上向け。小学校で習う漢字にはふりがなを付けない。
8: 一般向け。常用漢字にはふりがなを付けない。
無指定の場合、ひらがなを含むテキストにふりがなを付ける。
注1:学年は「小学校学習指導要領」の付録「学年別漢字配当表」(1989年3月15日文部科学省告示。1992年4月施行)を参考に設定されている。
注2:JIS X 0208が定める漢字
応答データ構造(json) id id jsonrpc 2.0 result word surface 単語の表記 furigana 単語のよみ(平仮名) roman 単語のよみ(ローマ字) word surface 単語の表記 furigana 単語のよみ(平仮名) roman 単語のよみ(ローマ字) subword surface 単語の表記 furigana 単語のよみ(平仮名) roman 単語のよみ(ローマ字) subword surface 単語の表記 furigana 単語のよみ(平仮名) roman 単語のよみ(ローマ字)

解説:読み仮名を取得

0220: /**
0221:  * 「Yahoo!JAPANルビ振りWebサービス」を用いて読み仮名を取得
0222:  * @param   string $sentenceルビを振るテキスト
0223:  * @param   array  $items    読み仮名を格納する配列
0224:  * @param   int    $grade    学年(1~8,無指定):省略時 無指定
0225:  * @return  bool TRUE:成功/FALSE:失敗
0226: */
0227: function getRuby_Yahoo($sentence, &$items$grade='') {
0228:     $url = 'https://jlp.yahooapis.jp/FuriganaService/V2/furigana';
0229:     $post = array(
0230:         'id'       => '1234-1',                           //ダミー
0231:         'jsonrpc'  => '2.0',                          //固定値
0232:         'method'   => 'jlp.furiganaservice.furigana', //固定値
0233:         'params'   => array(
0234:             'grade'    => (int)$grade,                 //学年
0235:             'sentence' => (string)$sentence                //対象テキスト
0236:         )
0237:     );
0238:     $json = json_encode($post);
0239: 
0240:     //WebAPIにパラメータをPOST渡しする
0241:     $stream = stream_context_create(array('http' => array(
0242:         'header'  => "Content-Type: application/json\r\n" .
0243:                     "User-Agent: Yahoo AppID: " . $this->YAHOO_APPLICATION_ID . "\r\n",
0244:         'method'  => 'POST',
0245:         'content' => $json,
0246:     )));
0247: 
0248:     //WebAPIリクエスト
0249:     $this->webapi = $url;
0250:     $res = file_get_contents($urlFALSE$stream);
0251: 
0252:     if ($res == FALSE) {
0253:         $this->error  = TRUE;
0254:         $this->errmsg = 'WebAPI error: ' . $url;
0255:         return FALSE;
0256:     }
0257: 
0258:     //応答を配列へ代入
0259:     $results = json_decode($res);
0260:     if (! isset($results->result->word))   return FALSE;
0261:     $i = 0;
0262:     foreach ($results->result->word as $word) {
0263:         $items[$i]['surface'] = isset($word->surface) ?
0264:             (string)$word->surface : '';
0265:         $items[$i]['furigana'] = isset($word->furigana) ?
0266:             (string)$word->furigana : '';
0267:         $items[$i]['roman'] = isset($word->roman) ?
0268:             (string)$word->roman : '';
0269:         //subwordを配列へ代入
0270:         if (isset($word->subword)) {
0271:             $j = 0;
0272:             foreach ($word->subword as $subword) {
0273:                 $items[$i]['subword'][$j]['surface'] =
0274:                     isset($subword->surface) ? (string)$subword->surface : '';
0275:                 $items[$i]['subword'][$j]['furigana'] =
0276:                     isset($subword->furigana) ? (string)$subword->furigana : '';
0277:                 $items[$i]['subword'][$j]['roman'] =
0278:                     isset($subword->roman) ? (string)$subword->roman : '';
0279:                 $j++;
0280:             }
0281:         }
0282:         $i++;
0283:     }
0284: 
0285:     return TRUE;
0286: }

WebAPIを呼び出して結果を受け取るのはユーザー関数 getRuby_Yahoo である。結果は配列変数 $items に格納する。

WebAPIに渡すパラメータは  json_encode  関数によってJSON文字列にエンコードする。
User-Agentとして Yahoo! JAPAN Webサービス アプリケーションID を渡すために、 stream_context_create  関数を使ってストリームコンテキストを用意し、 file_get_contents  関数を使って応答JSON文を取得する。

応答JSON文は  json_decode  関数を使って連想配列にデコードし、配列 $items へ格納していく。
単語が漢字かな交じりのとき、その単語を、さらに細かく漢字部分とひらがな部分に分割した結果のリスト(subword)が含まれているときは、それも格納するようにした。この情報を利用し、漢字のみにルビを振ることができるようにする。

解説:パラメータ受け

0178: /**
0179:  * 指定したパラメータを取り出す
0180:  * @param   string $key  パラメータ名(省略不可)
0181:  * @param   bool   $auto TRUE=自動コード変換あり/FALSE=なし(省略時:TRUE)
0182:  * @param   mixed  $def  初期値(省略時:空文字)
0183:  * @return  stringパラメータ/NULL=パラメータ無し
0184: */
0185: function getParam($key$auto=TRUE$def='') {
0186:     if (isset($_GET[$key]))         $param = $_GET[$key];
0187:     else if (isset($_POST[$key]))   $param = $_POST[$key];
0188:     else                            $param = $def;
0189:     if ($auto)  $param = mb_convert_encoding($paramINTERNAL_ENCODING, 'auto');
0190:     return $param;
0191: }

0193: /**
0194:  * 指定したパラメータを取り出す(整数バリデーション付き)
0195:  * @param   string $key  パラメータ名(省略不可)
0196:  * @param   int    $def  デフォルト値(省略可)
0197:  * @param   int    $min  最小値(省略可)
0198:  * @param   int    $max  最大値(省略可)
0199:  * @return  int値/FALSE
0200: */
0201: function getParam_validateInt($key$def='', $min=0, $max=9999) {
0202:     //パラメータの存在チェック
0203:     if (isset($_GET[$key]))         $param = $_GET[$key];
0204:     else if (isset($_POST[$key]))   $param = $_POST[$key];
0205:     else                            $param = $def;
0206:     //整数チェック
0207:     if (preg_match('/^[0-9\-]+$/', $param) == 0)    return FALSE;
0208:     //最小値・最大値チェック
0209:     if ($param < $min || $param > $max)             return FALSE;
0210: 
0211:     return $param;
0212: }

URLパラメータを受け取るのに、テキストはユーザー関数 getParam で、学年(整数値)はユーザー関数 getParam_validateInt を定義している。ユーザー関数 getParam_validateInt はバリデーションを行う。

解説:ルビ振り

0246: /**
0247:  * ルビ振り結果をテキストに反映する
0248:  * @param   array  $items  ルビを格納した配列
0249:  * @param   int    $roman  0:平仮名(省略時),1:ローマ字
0250:  * @return  stringルビ振り結果
0251: */
0252: function setRuby($items$roman=0) {
0253:     $outstr = '';
0254:     foreach ($items as $val) {
0255:         if ($val['surface'] != $val['furigana']) {
0256:             $ruby = ($roman == 0) ? $val['furigana'] : $val['roman'];
0257:             if ($ruby != '') {
0258: $outstr .=<<< EOT
0259: <ruby><rb>{$val['surface']}</rb><rp style="color:blue;">(</rp><rt style="font-size:60%; color:blue;">{$ruby}</rt><rp style="color:blue;">)</rp></ruby>
0260: EOT;
0261:             } else {
0262:                 $outstr .= my_nl2br($val['surface']);
0263:             }
0264:         } else {
0265:             $outstr .= my_nl2br($val['surface']);
0266:         }
0267:     }
0268: 
0269:     return $outstr;
0270: }

ユーザー関数 getRuby で得た配列変数 $items をもとに <ruby> タグを付加するのがユーザー関数 setRuby である。
HTML5の仕様なのかブラウザの実装なのか分からなかったが、<ruby> タグ中に改行があると、表示時に半角スペースが入ってしまうことがある。やむを得ないので可読性は低下するが、改行を入れないようにした。

解説:漢字のみルビ振り

0272: /**
0273:  * ルビ振り結果をテキストに反映する:subword単位
0274:  * @param   array  $items  ルビを格納した配列
0275:  * @param   int    $roman  0:平仮名(省略時),1:ローマ字
0276:  * @return  stringルビ振り結果
0277: */
0278: function setRubySubword($items$roman=0) {
0279:     $outstr = '';
0280:     foreach ($items as $arr) {
0281:         //subwordのルビを振る
0282:         if (isset($arr['subword'])) {
0283:             foreach ($arr['subword'] as $val) {
0284:                 $ruby = ($roman == 0) ? $val['furigana'] : $val['roman'];
0285:                 if (($ruby != '') &&
0286:                     (preg_match('/^[ぁ-んァ-ヶ]+$/', $val['surface']) == 0)) {
0287: $outstr .=<<< EOT
0288: <ruby><rb>{$val['surface']}</rb><rp style="color:blue;">(</rp><rt style="font-size:60%; color:blue;">{$ruby}</rt><rp style="color:blue;">)</rp></ruby>
0289: EOT;
0290:                 } else {
0291:                     $outstr .= my_nl2br($val['surface']);
0292:                 }
0293:             }
0294:         //通常のルビを振る
0295:         } else if ($arr['surface'] != $arr['furigana']) {
0296:             $ruby = ($roman == 0) ? $arr['furigana'] : $arr['roman'];
0297:             if ($ruby != '') {
0298: $outstr .=<<< EOT
0299: <ruby><rb>{$arr['surface']}</rb><rp style="color:blue;">(</rp><rt style="font-size:60%; color:blue;">{$ruby}</rt><rp style="color:blue;">)</rp></ruby>
0300: EOT;
0301:             } else {
0302:                 $outstr .= my_nl2br($arr['surface']);
0303:             }
0304:         //ルビを振らない
0305:         } else {
0306:             $outstr .= my_nl2br($arr['surface']);
0307:         }
0308:     }
0309:     return $outstr;
0310: }

ユーザー関数 setRubySubword は、前述の関数 setRuby に加え、単語が漢字かな交じりのとき(subword があるとき)、漢字のみにルビを振る。
 preg_match  を使って、表記が平仮名または片仮名のみの時にはルビを振らないようにしている。

解説:改行変換

0237: /**
0238:  * \nのみを<br />に変換
0239:  * @param   string $str入力テキスト
0240:  * @return  string変換後テキスト
0241: */
0242: function my_nl2br($str) {
0243:     return preg_replace("/\n/ui", "<br />", $str);
0244: }

ユーザー関数 getRubysetRubySubword でルビを振っている最中、改行文字が現れたら、それを <br /> タグに変換するユーザー関数が my_nl2br である。
最初、 nl2br  を使ってみたのだが、入力した改行文字 \n の実体が CR+LF だった場合、APIの仕様で CRLFの2文字に分解されるらしく、改行が2つ続いてしまう。そこで、オリジナルの関数を用意した。

活用例

みんなの知識 ちょっと便利帳では、「漢字に振り仮名/ルビを付ける」のコーナーで、このサンプル・プログラムを活用している。ありがとうございます。

参考サイト

(この項おわり)
header