PHPで異体字を表示する

(1/1)
PHPで異体字を表示する
外字が不要に――ISO/IEC 10646:2017」では、IPAなどが進める文字整備基盤事業で、「ISO/IEC 10646:2017」として6万字を超える漢字が収録されたことを解説した。その中には、渡邊の「」など、多くの異体字を含んでいる。
そこで今回は、ISO/IEC 10646:2017の異体字を表示するPHPプログラムを作ってみることにする。
(2021年5月9日)画像表示を文字情報技術促進協議会サイトへ更新。
(2021年2月6日)-g オプションを追加。
(2021年1月2日)40ビット以上のUnicodeで文字化けが起きる不具合を修正。

目次

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

PHPで異体字を表示する
ホームページ上で異体字を画像として表示したいときなど、URLパラメータで -gオプション を使うことで実現できる。
たとえば
<img src="itaiji.php=?g=908A_E0109" /> は 邊 の異体字です。
は 邊 の異体字です。
のようにして使うことができる。-gオプション のあとに、IVS(後述)を指定する。

サンプル・プログラム

圧縮ファイルの内容
itaiji.phpサンプル・プログラム本体
mji.00601.sqlite3MJ文字情報一覧表(SQLite3データベース)
mji2sql.vbsMJ文字情報一覧表(Excelファイル)からSQLite3データベースを生成するVBScript

準備:MJ文字情報一覧表

IPAのMJ文字情報取得APIを利用することで、Unicode(コードポイント)から異体字のMJ文字図形を求めることができる。
だが、このAPIは試験的なものであり、2020年(令和2年)8月、文字情報基盤の成果物がIPAから一般社団法人文字情報技術促進協議会に移管したことから、停止する可能性がある。(2021年9月現在、利用できなくなっている)
そこで今回は、文字情報技術促進協議会に移管された「MJ文字情報一覧表」を利用することにする。

MJ文字情報一覧表は、前述の6万文字を超える漢字の一覧表である。
Strict Open XML形式のExcelファイルとしてダウンロードできるのだが、PhpExcelGoogle Spreadsheet などの既製ライブラリを使ってオープンすることができない。Excelも2007ではオープンできず、Microsoft 365 を使ってオープンできることが分かった。
そこで、ダウンロードしたMJ文字情報一覧表を、Microsoft 365 を搭載したPC上で VBScript を使って読み込み、SQLite データベースに変換し、PHPから利用できるようにする方針を立てた。

SQLite データベースを生成するため、SQLite ODBCドライバを用意する。
公式サイトから SQLite ODBC Driver をダウンロードする。64ビット版でも、32ビット版の "sqliteodbc.exe" をダウンロードし、インストールするといいようだ。

Microsoft 365SQLite ODBC Driver が用意できたら、次の手順で MJ文字情報一覧表をデータベースに変換する。
  1. MJ文字情報一覧表をダウンロードし、解凍する。"mji.00601.xlsx" という約7.5Mバイトのファイルができる。
  2. 同梱のVBScript "mji2sql.vbs" を実行する。MJ文字情報一覧表ファイルは変数 fnameMJ に、SQLiteデータベース・ファイルは DBname にフルパスで記述すること。DBname は書き込み権限があるパスを指定する。
  3. しばらくすると、SQLiteデータベース・ファイルができる。PHPから参照できるフォルダへ移動する。
変換した MJ文字情報一覧表(SQLiteデータベース)は、"mji.00601.sqlite3" として同梱しており、これをそのまま利用してもらって構わない。

MJ文字情報一覧表:テーブル定義

mj:MJ文字情報一覧表
No. 名前 内容
1MJcodeテキストMJ文字図形名
2ucsテキスト実装したUCS:プライマリキー
3ivsテキスト実装したMoji_JohoコレクションIVS
4kosekiテキスト戸籍統一文字番号
5jukiテキスト住基ネット統一文字コード
6sesakuテキスト漢字施策:常用漢字、人名漢字
1yomiテキスト読み(参考):音読みは片仮名、訓読みは平仮名。

準備:IPAmj明朝フォント

MJ文字情報一覧表にある異体字を表示できるのは、IPAmj明朝フォントである。
文字情報技術促進協議会からダウンロード、解凍すると、"ipamjm.ttf" という約46M バイトの巨大なフォントファイルが取り出せる。
これを、PHPから参照できるフォルダに配置する。

解説:初期値

  37: //入力文字(デフォルト)
  38: define('DEF_SOUR', '邊');
  39: 
  40: //MJ文字情報一覧表(SQLiteデータベース)
  41: define('DBFILE', './mji.00601.sqlite3');
  42: 
  43: //検索SQL(※変更不可)
  44: define('PRE_SELECT', 'SELECT MJcode, ivs FROM mj WHERE ucs=:ucs;');
  45: define('PRE_SELECT_IVS', 'SELECT MJcode, ivs FROM mj WHERE ivs=:ivs;');
  46: 
  47: //PNG画像データURL
  48: //define('URL_PNG', 'https://mojikiban.ipa.go.jp/');
  49: define('URL_PNG', 'https://moji.or.jp/mojikibansearch/img/MJ/');
  50: 
  51: //IPAmj明朝フォント・ファイルの場所
  52: define('IPAmj', '../../../../common/font/ipamjm.ttf');

冒頭で用意したMJ文字情報一覧表(SQLiteデータベース)検索は定数 DBFILE に、IPAmj明朝フォント・ファイルの場所は定数 IPAmj に記述する。

解説:Unicodeコードポイントを求める

 214: /**
 215:  * Unicodeコードポイントを求める
 216:  * @param   string $ch 1文字
 217:  * @return  string Unicodeコードポイント
 218: */
 219: function ch2ucp($ch) {
 220:     return sprintf("U+%0X", hexdec(bin2hex(mb_convert_encoding($ch, 'UCS-4', INTERNAL_ENCODING))));
 221: }

1文字(エンコードはUTF-8を想定)からUnicodeコードポイントを求めるユーザー関数が ch2ucp である。

解説:UnicodeコードポイントからUTF-8文字を求める

 223: /**
 224:  * UnicodeコードポイントからUTF-8文字を求める
 225:  * @param   string $ucp Unicodeコードポイント
 226:  * @return  string UTF-8文字
 227: */
 228: function ucp2ch($ucp) {
 229:     $ch = '';
 230:     if (preg_match('/U\+([0-9A-F]+)/ui', $ucp, $arr> 0) {
 231:         $bin = hex2bin(str_repeat('0', 8 - strlen($arr[1])) . $arr[1]);
 232:         $ch = mb_convert_encoding($bin, INTERNAL_ENCODING, 'UCS-4');
 233:     }
 234:     return $ch;
 235: }

ucp2chch2ucp の逆関数で、UnicodeコードポイントからUTF-8文字を求める。

解説:コードポイントから異体字を求める

 237: /**
 238:  * 異体字を求める:コードポイントから
 239:  * @param   string $ch    1文字
 240:  * @param   array  $items 情報を格納する配列
 241:  * @return  int 異体字の数/0:指定文字が見つからない/(-1):DBアクセスに失敗
 242: */
 243: function geiItaiji($ch, &$items) {
 244:     $ucp = ch2ucp($ch);
 245: 
 246:     //MJ文字情報一覧表(SQLiteデータベース)検索
 247:     try {
 248:         $pdo = new PDO('sqlite:' . DBFILE);
 249:         $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
 250:         $stmt = $pdo->prepare(PRE_SELECT);
 251:         $stmt->bindValue(':ucs', $ucp, PDO::PARAM_STR);
 252:         $stmt->execute();
 253:         $cnt = 0;
 254:         while ($row = $stmt->fetch()) {
 255:             //画像ファイルのURL
 256:             $items[$cnt]['png'] = URL_PNG . $row['MJcode'. '.png';
 257:             //HTMLエンティティ
 258:             if (preg_match('/([0-9A-Z]{4,6})\_([0-9A-Z]{5})/ui', $row['ivs'], $arr> 0) {
 259:                 $items[$cnt]['itaiji1'] = '&#x' . $arr[1. ';&#x' . $arr[2. ';';
 260:             } else if (preg_match('/U\+([0-9A-Z]{4,6})/ui', $ucp, $arr> 0) {
 261:                 $items[$cnt]['itaiji1'] = '&#x' . $arr[1. ';';
 262:             } else {
 263:                 $items[$cnt]['itaiji1'] = '';
 264:             }
 265:             //HTMLエンティティ(エンコード)
 266:             $items[$cnt]['itaiji2'] = preg_replace('/&/ui', '&amp;', $items[$cnt]['itaiji1']);
 267:             $cnt++;
 268:         }
 269:         $pdo = NULL;
 270:     } catch (PDOException $e) {
 271:         $cnt = -1;
 272:     }
 273: 
 274:     return $cnt;
 275: }

ユーザー関数 geiItaiji は、冒頭に用意したMJ文字情報一覧表(SQLiteデータベース)を検索し、異体字に関する情報(文字情報技術促進協議会のPNG画像ファイルのURL、異体字のHTMLエンティティ)を配列に格納する。

解説:IVSから異体字を求める

 277: /**
 278:  * 異体字を求める:IVSから
 279:  * @param   string $ivs   IVS
 280:  * @param   array  $items 情報を格納する配列
 281:  * @return  int 異体字の数/0:指定文字が見つからない/(-1):DBアクセスに失敗
 282: */
 283: function geiItaijiIVS($ivs, &$items) {
 284:     //MJ文字情報一覧表(SQLiteデータベース)検索
 285:     try {
 286:         $pdo = new PDO('sqlite:' . DBFILE);
 287:         $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
 288:         $stmt = $pdo->prepare(PRE_SELECT_IVS);
 289:         $stmt->bindValue(':ivs', $ivs, PDO::PARAM_STR);
 290:         $stmt->execute();
 291:         $cnt = 0;
 292:         while ($row = $stmt->fetch()) {
 293:             //画像ファイルのURL
 294:             $items[$cnt]['png'] = URL_PNG . $row['MJcode'. '.png';
 295:             //HTMLエンティティ
 296:             if (preg_match('/([0-9A-Z]{4,6})\_([0-9A-Z]{5})/ui', $row['ivs'], $arr> 0) {
 297:                 $items[$cnt]['itaiji1'] = '&#x' . $arr[1. ';&#x' . $arr[2. ';';
 298:             } else if (preg_match('/U\+([0-9A-Z]{4,6})/ui', $ucp, $arr> 0) {
 299:                 $items[$cnt]['itaiji1'] = '&#x' . $arr[1. ';';
 300:             } else {
 301:                 $items[$cnt]['itaiji1'] = '';
 302:             }
 303:             //HTMLエンティティ(エンコード)
 304:             $items[$cnt]['itaiji2'] = preg_replace('/&/ui', '&amp;', $items[$cnt]['itaiji1']);
 305:             $cnt++;
 306:         }
 307:         $pdo = NULL;
 308:     } catch (PDOException $e) {
 309:         $cnt = -1;
 310:     }
 311: 
 312:     return $cnt;
 313: }

ユーザー関数 geiItaijiIVS は、-gオプション のために、IVSをキーにしてMJ文字情報一覧表(SQLiteデータベース)を検索し、異体字に関する情報(文字情報技術促進協議会のPNG画像ファイルのURL、異体字のHTMLエンティティ)を配列に格納する。

参考サイト

(この項おわり)
header