PHPで機種依存文字の置き換え

#はじめに

※投稿後、いくつかコメント頂きました。主に、@mpyw さん、@hrdayaさんのコメントを元に、大幅に変更を加えています。コメントありがとうございました。

機種依存文字をスペースに置き換えたいと思い見つけた[ページ](http://memo.xight.org/2006-06-19-13)を試したのですが、うまくいかなかったので、その時に調べた事を書いておきます。なお、特に断りのない限り、文字コードはUTF8を想定しています。

#参考にしたソース

参考ページには次のように書いてありました。

```php
function replaceText($str){
    $arr = array(
        ... (省略) ...
        '\xE2\x91\xA0' => '(1)',
        '\xE2\x91\xA1' => '(2)',
        '\xE2\x91\xA2' => '(3)',
        '\xE2\x91\xA3' => '(4)',
        ... (省略) ...

    );
    return str_replace(array_keys($arr),array_values($arr), $str);
}
```

これのやりたい事は、次のソースと等価です（~~環境依存なもので表示されなかったらすいません、~~ Windowsで入力すると環境依存と書いてあるので、正しく表示されない事もあるかもしれません。丸で囲まれた数字です。この記事では環境依存文字として扱います）。

```php
function replaceText($str){

    $arr = array(
        ... (省略) ...
        '①' => '(1)',
        '②' => '(2)',
        '③' => '(3)',
        '④' => '(4)',
        ... (省略) ...

    );
    return str_replace(array_keys($arr),array_values($arr), $str);
}
```

このように環境依存文字をソースコードに書くと、後で別の環境で見た時に意味不明になる可能性があるので避けたいです。なので、上の例のような形で置き換えをするのがベターだと思います。なお、今回の記事を書くキッカケになったのは、'😀'の時がFirefoxでは表示され、Chromeでは文字化けする事でした。これは恐らくフォントの問題だと思います。

#修正版

そもそも、大半の言語では１６進数の表現は大抵0xFFのような形なので、なんでこんな事をやってるのだろう？と思い、よくよく[マニュアル](http://php.net/manual/en/regexp.reference.escape.php)を見ると、\x**は正規表現の１６進数表現でした。従って次のように修正しました。

```php
function replaceText($str){
    $arr = array(
        ... (省略) ...
        // 0x2460 - 0x246F
        '/\xE2\x91\xA0/' => '(1)',
        '/\xE2\x91\xA1/' => '(2)',
        '/\xE2\x91\xA2/' => '(3)',
        '/\xE2\x91\xA3/' => '(4)',
        ... (省略) ...
    );
    return preg_replace(array_keys($arr),array_values($arr),$str);
}
```

これで置き換えが上手くいきました (^^)v (機種依存文字をさりげなく避けています)。なお、初めはmb_regex_encodingなども必要かと思っていましたが、@mpywさんの指摘により不要という事が分かりました。詳しくはコメントを見て下さい。

また、@mpywさんの指摘により、次のようにダブルクォーテーションで囲めば、正規表現でなくても置き換え出来ることが分かりました。これもコメントを見ると、もう少しシンプルなプログラムが紹介されています。

```php

function replaceText($str){
    $arr = array(
        ... (省略) ...
        "\xE2\x91\xA0" => '(1)',
        "\xE2\x91\xA1" => '(2)',
        "\xE2\x91\xA2" => '(3)',
        "\xE2\x91\xA3" => '(4)',
        ... (省略) ...

    );
    return str_replace(array_keys($arr),array_values($arr), $str);
}

```

でもプログラムのコメントにあるように、UTFでは①（丸１）は[0x2460で表現されます](http://www.unicode.org/charts/PDF/U2460.pdf)。では何で、'\xE2\x91\xA0/'でうまくいくのでしょう？

#文字列の内部表現

で、ここで試してみたのが`bin2hex`です。

```php
echo bin2hex('①');    //結果は0xE291A0
```

プログラムの正規表現とマッチします。なお、今回は参考ソースに置き換えしたい文字が無かったので、`bin2hex`でを使って置き換えの正規表現を書きました。

#UTF8について

ここでは、指摘されたコメントを元に調べた事について書きます。もし置き換えだけ出来ればいーやという方は無視して下さい。

別のセクションで次のように書きました。

>UTFでは①（丸１）は[0x2460で表現されます](http://www.unicode.org/charts/PDF/U2460.pdf)。では何で、'\xE2\x91\xA0/'でうまくいくのでしょう？

これは少し<b>間違っています</b>。0x2460というのは、Unicodeで定義されている文字コードです。これを、UTF-8の方式で変換したものが、0xE291A0になります。この辺りの定義の理解があいまいだったのですが、Unicodeは文字集合とそれに対応する文字コードで、UTF-8はその文字コードを符号化する方式す。変換の方法などは、[UTF8のWikipedia](http://en.wikipedia.org/wiki/UTF-8)か、[本当は怖い文字コードの話](http://gihyo.jp/admin/serial/01/charcode/0004)が分かり易かったです。

#bin2hexについて

このセクションは完全に蛇足なのですが、過去に書いた勘違いを残しておこうと思い、書いておきます。

[bin2hexのマニュアル](http://php.net/manual/ja/function.bin2hex.php)を見ると次のように書いてあります。

>str を16進表現に変換した<b>ASCII文字列</b>を返します。変換は、上位ニブルからバイト毎に行われます。

~~これは想像ですが、PHPの内部ではASCII文字列で格納されていて、表示の時にはそのASCIIコードを任意の文字コード（ここではUTF8）に変換しているのでしょう。厳密に言うと機種依存文字はASCII文字列とは言えないので、PHPでの拡張ASCII文字列という感じでしょうが。そうだとすると、実験はしてないですが、UTF8でも同じ関数で置き換えが出来るはずです。もしこの辺りに詳しい方がいらっしゃいましたら、教えて下さると有難いです。~~

この意味を完全に勘違いしていたのですが、bin2hexはバイナリデータをASCII形式のHEXで返すと書いてあります。従って、文字データはバイナリデータで保存されています。この時のデータ形式はUTF8です。bin2hexで返ってくるHEXデータは、UnicodeをUTF-8で符号化したものと一致します。['あ'のUTF8コードは0xE38182](http://gihyo.jp/admin/serial/01/charcode/0004)ですが、``

```bash

$ php -r "echo bin2hex('あ').PHP_EOL;"
e38182
```

の、ような結果になります。