hnwの日記

mb_check_encodingは何をチェックするのか(その2 EUC-JP編)

前回に引き続き、PHPのmb_check_encoding関数について調べてみます。今回はEUC-JP、eucJP-win、CP51932の3つについて調べてみました。

EUC-JP

EUC-JPというのは0xA1から0xFEまでの2バイトの組み合わせでJISX0208漢字(いわゆるJIS第一水準&第二水準)を、SS2(0x8E)からはじまる2バイトでJISX0201カナ(いわゆる半角カナ)を、SS3(0x8F)からはじまる3バイトでJISX0212補助漢字を、それぞれ表現するようなエンコーディング形式です。


EUC-JPに対するmb_check_encodingの挙動はシンプルで、次のものがtrueになります。

  • 0xA1A1(JISX0208 1区1点)から0xFEFE(JISX0208 94区94点)まで
  • 0x8EA1(半角カナの句点「。」)から0x8EDF(半角カナの半濁点「゜」)まで
    • 0x8EE0から0x8EFEまではfalseとなりますが、他との整合性の意味で疑問です。字体が定義されていないのでfalseということなら、字体が未定義なのにtrueになる文字が他に多数あるので、ここだけfalseにするのは微妙な気がします。
  • 0x8FA1A1(JISX0212 1区1点)から0x8FFEFE(JISX0212 94区94点)まで
    • ただし、0x8FA2B7のみfalse。これは全角チルダ「〜」であり、重複文字であるため。


これ以外のパターンは全てfalseとなります。つまり、次のような場合は全て不正な文字列として扱われます。

  • 1byteでは存在し得ない文字で終わっている文字列。つまり、1byte文字または2byte文字に続いて0x80から0xFFまでの1byteがある場合。
  • 2byte文字の1byte目に続いて2byte目には有り得ない1byteが続く場合。0xA1 0xA0など。
  • 3byte文字の1byte目に続いて2byte目には有り得ない1byteが続く場合。0x8F 0xA0 0xA1など。
  • 3byte文字の1byte目&2byte目に続いて3byte目には有り得ない1byteが続く場合。0x8F 0xA1 0xA0など。

eucJP-win

eucJP-winというのは、EUC-JPに加えてIBM拡張文字やら何やらを含んでるもの、というくらいの認識で良いかと思います。SJIS-winのEUC版、くらいに考えておけば大抵は問題ないでしょう。


具体的には、JISX0208の13区にNEC特殊文字、JISX0212の83区から84区にIBM拡張文字の一部が割り当てられていること、またユーザ定義文字領域としてJISX0208の85区から94区、およびJISX0212の85区から94区までを割り当てているのが違いになります。詳しくは森山さんのページ「eucJP-ms」を参照してください。


mb_check_encodingの挙動は、EUC-JPに比べて下記の文字が余計に不正と見なされます。

  • NEC特殊文字の9文字
    • 0xADF0(≒), 0xADF1(≡), 0xADF2(∫), 0xADF5(√), 0xADF6(⊥), 0xADF7(∠), 0xADFA(∵), 0xADFB(∩), 0xADFC(∪)
  • IBM拡張文字の13文字
    • 0x8FF3FDから0x8FF3FE(ローマ数字のIとII)
    • 0x8FF4A1から0x8FF4A8(ローマ数字のIIIからX)
    • 0x8FF4AB (株)
    • 0x8FF4AC No.
    • 0x8FF4AD TEL
  • 0x8FA2F1(補助漢字2区の「No.」)


これらの文字は全て代替の文字があるためfalseになります。これは望ましい動作かどうかは疑問ですが、現在の仕様としては理解できる挙動です。さらに、これとは別に次の文字もfalseを返します。

  • 0x8FF3A1から0x8FF3F2まで(補助漢字83区、IBM拡張文字が字体として割り当てられていない領域)


半角カナと同様、字体が割り当てられていないからといってfalseを返すのは疑問に思います。

CP51932

CP51932というのは、Microsoft製品の理解するEUC-JP、くらいの認識で良いと思います。IEなどが理解するEUC-JPというのは、UNIXの世界でよく使われているEUC-JPとは少し違うんだそうです。


EUC-JPとの具体的な違いは下記の通りです。これも詳しくは森山さんの記事「eucJP-ms と CP51932 の違い」を参照するのがいいと思います。

  • 0x8Fから始まる3バイト文字は無効、補助漢字を表現する方法は無い
  • JISX0208 13区にNEC特殊文字を割り当て
  • JISX0208 89区から92区にNEC選定IBM拡張文字を割り当て


mb_check_encodingの挙動は、EUC-JPのときの挙動に対して、下記の文字でfalseを返します。

  • 0x8Fから始まる3バイト文字
  • NEC特殊文字の9文字
    • 0xADF0(≒), 0xADF1(≡), 0xADF2(∫), 0xADF5(√), 0xADF6(⊥), 0xADF7(∠), 0xADFA(∵), 0xADFB(∩), 0xADFC(∪)
  • NEC選定IBM拡張文字の0xFCFB「¬」
  • NEC選定IBM拡張文字の字体が未定義の2文字
    • 0xFCEF、0xFCF0
    • この文字に相当するSJIS-winの文字(0xEEED、0xEEEE)は、mb_check_encodingがtrueを返します。カオスですね。
  • JISX0208 85区から88区、JISX93区から94区
    • Perl正規表現で言うと[\xF5-\xF8\xFD\xFE][\xA1-\xFE]な文字
    • EUC-JPではtrueになる領域ですが、扱いが違うんでしょうか。謎です。

まとめ

前回の記事を書いた後のやりとりで、mb_check_encoding関数ってあまり使いどころが無いのかな、と感じるようになりました。不正な文字がないかどうかのチェックをしたいのであれば、重複文字を不正とみなすのは致命的です。


もちろん、UTF-8などUnicode系のエンコーディングのチェックにはmb_check_encodingは活躍すると思います。Unicode同士であれば重複文字はありませんし、実験した範囲では、冗長なUTF-8表現など、不正なUTF-8をちゃんと検出するようです。


また、今回の調査結果からすると、EUC-JPまたはeucJP-winの正当性のチェックにならmb_check_encoding($str, "EUC-JP")が使えそうですね。補助漢字の「〜」を誤判定しますけど、この文字を作り出すのって結構難しいんじゃないでしょうか。まあ、それを言い出したらSJIS-winでも重複文字を作るのは難しいかもしれませんが…。