PHPで見た目が同じ違う漢字に立ち向かう

掲載日

経緯

あるシンプルな検索機能(SQLで LIKE '%キーワード%'としているような本当に単純な検索)で、タイトルに「市民」と入っているにも関わらず、「市民」で検索しても引っかからない記事があるというお問い合わせを受けました。

文字コード変換ツールを実行したスクリーンショット。

解析ツールサイトを使って文字コードを調査したところ、どうも「民」の文字コードが異なっており、通常の「民」のUnicodeは「E6 B0 91」となるところを、「E2 BA A0」となっておりました。

CJK部首補助というもので、本来部首を表すための漢字とのこと。
漢字の解説などで使用するケース以外ではほぼほぼ使用しないと思われます。

元の入力が誤っていなくても、PDF等から漢字をコピーペーストするとこうなることがあるようです。
そして少なくとも自分の構築したサイトで使用しているフォント上はまったく同じ形で出力されてしまうため、ブラウザ上では判別できず非常に厄介。
 

さくらエディタに部首補助漢字を貼り付けて見た目に違うことが分かるプレビュー画像。

添付のようにさくらエディタに張り付ければ見た目の違いも分かります。

解決

文字コードやフォントについて詳しくないですが、こういったものは最初の問合せのような「確かにその文字が入っている(ように見える)のに検索に引っかからない」事態にならないためにも、全て一般的な漢字に変換したいです。

PHPにはNormalizerというクラスがあり、文字列の正規化が可能です。

正規化とは、文字や文字列を正式に決められた表現に変換する処理のことです。 これは、検索や並べ替えなどの際に比較をするときに特に重要となります。 ただ、それだけではなく、テキストを保存する際に表記を統一するために用いることもあります。

Normalizer クラスより引用

とのことなので、PHPで以下の処理を挟んであげれば問題ないのでは?と思いましたがそうは問屋が卸さない。

$normalized = Normalizer::normalize($str, Normalizer::FORM_C);

以下のような確認用のスクリプトを作って実行したところ、直っておらず。

<?php
// 部首補助置換テスト
$bad = "⺠";
$normalized = Normalizer::normalize($bad, Normalizer::FORM_C);

echo sprintf("→になってて欲しい: 民\n");
echo sprintf("%s => %s\n", $bad, $normalized);

echo sprintf("%s \n", $bad === $normalized ? "直ってない" : "直った");
スクリプトを実行した結果のスクリーンショット。民が部首補助漢字のままであることが確認できる。

 

調べたところ、「部首文字と統合漢字は別扱い」とのこと。難しい…。

統合漢字ではない(別物扱いなので、正規化のマップが無い)ため、こういったものは手動で変換ツールを作るか、有志のライブラリを使用するしかないようです。

今回はこちらのサイトの一覧を使用させて頂いて、自前で変換する処理を作りました。

class CJK
{
	const CJK_MAP = [
		"\u{2E83}" => "\u{4E5A}",
		"\u{2E85}" => "\u{4EBB}",
		"\u{2E87}" => "\u{20628}",
		~ (中略) ~
		"\u{2FD3}" => "\u{9F8D}",
		"\u{2FD4}" => "\u{9F9C}",
		"\u{2FD5}" => "\u{9FA0}"
	];

	public static function convert($text)
	{
		return strtr($text, self::CJK_MAP);
	}
}

以下のように呼び出して使用できます。

$result = CJK::convert("民");

これを確認用スクリプトで以下のように差し替えて実行します。


require_once __DIR__ . '/cjk.php';

// 部首補助置換テスト
$bad = "⺠";
$normalized = CJK::convert($bad);

echo sprintf("→になってて欲しい: 民\n");
echo sprintf("%s => %s\n", $bad, $normalized);

echo sprintf("%s \n", $bad === $normalized ? "直ってない" : "直った");

添付スクリーンショットの通り普通の漢字に直ります。

スクリプトを実行した結果のスクリーンショット。「民」が一般的な漢字に戻ったことが確認できる。

まとめ

  • 確かにその文字があるはずなのに検索に引っ掛からない場合、文字コード変換して一般的な文字コードになっているか確認
  • Normalize等で変換できるなら変換する。
  • 駄目なら有志ライブラリや自前の変換スクリプトを作成して置き換えを行う。

参考リンク

記事の作成者のA.W.のアイコン

この記事を書いた人

A.W.
茨城県在住Webエンジニアです。 PHP、PostgreSQL、Linuxなどを業務で使用しています。 趣味ではGoやNuxt、Flutterをやってます。

Comment

関連記事

check コピーしました!