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

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

(2020 年 7 月 25 日)<rp>タグの誤りを修正。
(2020 年 7 月 11 日)学年選択、ローマ字選択機能を追加
(2020 年 6 月 30 日)pahooNormalizeText::http メソッド内の  each  関数を foreach 文で代替;PHP7.2 対応。参考サイト表示を修正。

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

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 サービス」は、入力パラメータ(IN)として GET、POST の 2種類の方式を、出力結果(OUT)が XML で戻るという API である。
入力テキストは GET 渡しでは長くなりすぎてしまうので、POST 渡しを使うことにする。
WebAPIのURL
URL
https://jlp.yahooapis.jp/FuriganaService/V1/furigana

入力パラメータ
フィールド名 要否 内  容
appid 必須 アプリケーションID
sentence 必須 ルビを振る日本語テキスト。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が定める漢字
応答データ構造(xml) ResultSet Result WordList Word Surface 単語の表記 Furigana 単語のよみ(平仮名) Roman 単語のよみ(ローマ字) Word Surface 単語の表記 Furigana 単語のよみ(平仮名) Roman 単語のよみ(ローマ字) SubWordList 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:     //WebAPIにパラメータをPOST渡しする
0229:     $url = 'https://jlp.yahooapis.jp/FuriganaService/V1/furigana';
0230:     $sentence = urlencode($sentence);
0231:     $post = array(
0232:         'appid'        => $this->YAHOO_APPLICATION_ID,
0233:         'grade'        => $grade,
0234:         'sentence'     => $sentence
0235:     );
0236: 
0237:     //API呼び出し
0238:     $this->webapi = $url;
0239:     $res = $this->http($url, 'POST', '', $post);
0240:     if ($res == FALSE) {
0241:         $this->error  = TRUE;
0242:         $this->errmsg = 'WebAPI error: ' . $url;
0243:         return FALSE;
0244:     }
0245: 
0246:     $i = 0;
0247:     //PHP4用; DOM XML利用
0248:     if ($this->isphp5over() == FALSE) {
0249:         $dom = @domxml_open_mem($res);
0250:         if ($dom == FALSE)  return FALSE;
0251:         //ルビ
0252:         if (($ResultSet = $dom->get_elements_by_tagname('ResultSet')) == NULL)   return FALSE;
0253:         $Result = $ResultSet[0]->get_elements_by_tagname('Result');
0254:         foreach ($Result as $val1) {
0255:             $WordList = $val1->get_elements_by_tagname('WordList');
0256:             foreach ($WordList as $val2) {
0257:                 $Word = $val2->get_elements_by_tagname('Word');
0258:                 foreach ($Word as $val3) {
0259:                     if (($node = $val3->get_elements_by_tagname('SubWordList')) != NULL) {
0260:                         $SubWord = $node[0]->get_elements_by_tagname('SubWord');
0261:                         foreach ($SubWord as $val4) {
0262:                             $node1 = $val4->get_elements_by_tagname('Surface');
0263:                             $items[$i]['surface'] = $node1[0]->get_content();
0264:                             if (($node1 = $val4->get_elements_by_tagname('Furigana')) != NULL) {
0265:                                 $items[$i]['furigana'] = $node1[0]->get_content();
0266:                             } else {
0267:                                 $items[$i]['furigana'] = $items[$i]['surface'];
0268:                             }
0269:                             if (($node1 = $val4->get_elements_by_tagname('Roman')) != NULL) {
0270:                                 $items[$i]['Roman'] = $node1[0]->get_content();
0271:                             } else {
0272:                                 $items[$i]['Roman'] = $items[$i]['surface'];
0273:                             }
0274:                             $i++;
0275:                         }
0276:                     } else {
0277:                         $node2 = $val3->get_elements_by_tagname('Surface');
0278:                         $items[$i]['surface'] = $node2[0]->get_content();
0279:                         if (($node2 = $val3->get_elements_by_tagname('Furigana')) != NULL) {
0280:                             $items[$i]['furigana'] = $node2[0]->get_content();
0281:                         } else {
0282:                             $items[$i]['furigana'] = $items[$i]['surface'];
0283:                         }
0284:                         if (($node2 = $val3->get_elements_by_tagname('Roman')) != NULL) {
0285:                             $items[$i]['Roman'] = $node2[0]->get_content();
0286:                         } else {
0287:                             $items[$i]['Roman'] = $items[$i]['surface'];
0288:                         }
0289:                         $i++;
0290:                     }
0291:                 }
0292:             }
0293:         }
0294: 
0295:     //PHP5用; SimpleXML利用
0296:     } else {
0297:         $ResultSet = simplexml_load_string($res);
0298:         if (! isset($ResultSet->Result->WordList)) return FALSE;
0299:         //ルビ
0300:         foreach ($ResultSet->Result->WordList as $WordList) {
0301:             foreach ($WordList->Word as $Word) {
0302:                 if (isset($Word->SubWordList)) {
0303:                     foreach ($Word->SubWordList->SubWord as $SubWord) {
0304:                         $items[$i]['surface'] = (string)$SubWord->Surface;
0305:                         if (isset($SubWord->Furigana)) {
0306:                             $items[$i]['furigana'] = (string)$SubWord->Furigana;
0307:                         } else {
0308:                             $items[$i]['furigana'] = $items[$i]['surface'];
0309:                         }
0310:                         if (isset($SubWord->Roman)) {
0311:                             $items[$i]['Roman'] = (string)$SubWord->Furigana;
0312:                         } else {
0313:                             $items[$i]['Roman'] = $items[$i]['surface'];
0314:                         }
0315:                         $i++;
0316:                     }
0317:                 } else {
0318:                     $items[$i]['surface'] = (string)$Word->Surface;
0319:                     if (isset($Word->Furigana)) {
0320:                         $items[$i]['furigana'] = (string)$Word->Furigana;
0321:                     } else {
0322:                         $items[$i]['furigana'] = $items[$i]['surface'];
0323:                     }
0324:                     if (isset($Word->Furigana)) {
0325:                         $items[$i]['Roman'] = (string)$Word->Roman;
0326:                     } else {
0327:                         $items[$i]['Roman'] = $items[$i]['surface'];
0328:                     }
0329:                     $i++;
0330:                 }
0331:             }
0332:         }
0333:     }
0334: 
0335:     return TRUE;
0336: }

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

解説:パラメータ受け

0128: /**
0129:  * 指定したパラメータを取り出す
0130:  * @param string $key  パラメータ名(省略不可)
0131:  * @param bool   $auto TRUE=自動コード変換あり/FALSE=なし(省略時:TRUE)
0132:  * @param mixed  $def  初期値(省略時:空文字)
0133:  * @return string パラメータ/NULL=パラメータ無し
0134: */
0135: function getParam($key$auto=TRUE$def='') {
0136:     if (isset($_GET[$key]))         $param = $_GET[$key];
0137:     else if (isset($_POST[$key]))   $param = $_POST[$key];
0138:     else                            $param = $def;
0139:     if ($auto)  $param = mb_convert_encoding($paramINTERNAL_ENCODING, 'auto');
0140:     return $param;
0141: }

0143: /**
0144:  * 指定したパラメータを取り出す(整数バリデーション付き)
0145:  * @param string $key  パラメータ名(省略不可)
0146:  * @param int    $def  デフォルト値(省略可)
0147:  * @param int    $min  最小値(省略可)
0148:  * @param int    $max  最大値(省略可)
0149:  * @return int 値/FALSE
0150: */
0151: function getParam_validateInt($key$def='', $min=0, $max=9999) {
0152:     //パラメータの存在チェック
0153:     if (isset($_GET[$key]))         $param = $_GET[$key];
0154:     else if (isset($_POST[$key]))   $param = $_POST[$key];
0155:     else                            $param = $def;
0156:     //整数チェック
0157:     if (preg_match('/^[0-9\-]+$/', $param) == 0)   return FALSE;
0158:     //最小値・最大値チェック
0159:     if ($param < $min || $param > $max)             return FALSE;
0160: 
0161:     return $param;
0162: }

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

解説:ルビ振り

0187: /**
0188:  * ルビ振り結果をテキストに反映する
0189:  * @param array  $items  ルビを格納した配列
0190:  * @param int    $roman  0:平仮名(省略時),1:ローマ字
0191:  * @return string ルビ振り結果
0192: */
0193: function setRuby($items$roman=0) {
0194:     $outstr = '';
0195: 
0196:     foreach ($items as $val) {
0197:         if ($val['surface'] != $val['furigana']) {
0198:             $ruby = ($roman == 0) ? $val['furigana'] : $val['Roman'];
0199: $outstr .=<<< EOT
0200: <ruby>
0201: <rb>{$val['surface']}</rb>
0202: <rp style="color:blue;">(</rp>
0203: <rt style="font-size:60%; color:blue;">{$ruby}</rt>
0204: <rp style="color:blue;">)</rp>
0205: </ruby>
0206: 
0207: EOT;
0208:         } else {
0209:             $outstr .= $val['surface'];
0210:         }
0211:     }
0212: 
0213:     return $outstr;
0214: }

ユーザー関数 getRuby で得た配列変数 $items をもとに <ruby> タグを付加するのがユーザー関数 setRuby である。

活用例

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

参考サイト

(この項おわり)
header