[PHP-dev 1357] Re: PHP 5.2.0 以前と PHP 5.2.1RC1 以降で発生する一部の文字コード変換の非互換について

Moriyoshi Koizumi moriyoshi @ at.wakwak.com
2007年 1月 23日 (火) 18:50:14 JST


小泉です。
# おひさしぶりです。

[PHP-dev 1345]  PHP への CP932 系エンコーディングの追加パッチ
にて、森山さんが

>    具体的には、次の文字の変換が可能になります。
>
>    文字 SJIS     EUC      JIS       Unicode   Unicode での
>          コード値 コード値 コード値  コード値  文字の名前
>    ----- -------- -------- --------- --------  ----------------------
>     ―    0x815C   0xA1BD   0x213D    U+2015    HORIZONTAL BAR
>     〜    0x8160   0xA1C1   0x2141    U+FF5E    FULLWIDTH TILDE
>     ‖    0x8161   0xA1C2   0x2142    U+2225    PARALLELE TO
>     −    0x817C   0xA1DD   0x215D    U+FF0D    FULLWIDTH HYPHEN-MINUS
>     ¢    0x8191   0xA1F1   0x2171    U+FFE0    FULLWIDTH CENT SIGN
>     £    0x8192   0xA1F2   0x2172    U+FFE1    FULLWIDTH POUND SIGN
>     ¬    0x81CA   0xA2CC   0x224C    U+FFE2    FULLWIDTH NOT SIGN
>

と記しているので、このパッチのタイミングで挙動が変わったという
ことになるかと思います。

SJIS-Win が MSCP932 を基準にしていることは名前から自明なので、
FULLWIDTH TILDE に変換する挙動が正しいといえます。

逆変換についてはどうなるか以下のような簡単なプログラムを書いて
試してみました。 (cygwin で -lole32 -luuid でビルドできます)

--------------------------------------------------------------
#include <stdio.h>
#include <mlang.h>
#include <objbase.h>

int main()
{
    IMultiLanguage *ml = NULL;

    CoInitialize(NULL);

    if (CoCreateInstance(CLSID_CMultiLanguage, NULL,
            CLSCTX_INPROC_SERVER, IID_IMultiLanguage,
            reinterpret_cast<void**>(&ml)) != S_OK)
        goto out;

    do {
        DWORD dwMode = 0;
        // FULL-WIDTH TILDE
        static WCHAR in_str[] = { 0xff5e };
        // WAVE DASH の場合以下をコメントアウト
        // static WCHAR in_str[] = { 0x301c };
        UINT in_str_sz = sizeof(in_str) / sizeof(in_str[0]);
        CHAR out_str[1024];
        UINT out_str_sz = sizeof(out_str);
        UINT i;

        if (ml->ConvertStringFromUnicode(&dwMode, (DWORD)932, in_str,
                &in_str_sz, out_str, &out_str_sz) != S_OK) {
            printf("oops\n");
            break;
        }

        printf("%s\n", out_str);
    } while (0);


out:
    if (ml) ml->Release();
    CoUninitialize();

    return 0;
}
--------------------------------------------------------------
実験の結果

FULLWIDTH-TILDE => MSCP932 の変換: OK
WAVE DASH       => MSCP932 の変換: NG

となりました。

SJIS-Open を定義している
「Unicode とユーザ定義文字・ベンダ定義文字に関する問題点と解決策」
http://web.archive.org/web/20050312031416/www.opengroup.or.jp/jvc/cde/ucs-conv.html

によれば、

> 解決としては,
>
> 1. UCS からの変換の場合, 似たような字形をもつ複数の UCS のコードポイン
> ト が JIS X 0208 の一つのコードポイントに変換される「多対一」の変換に
> する. たとえば, 上の「?」の例では, UCS のコードポイント 0x301C と
> 0xFF5E の両方が JIS X 0208 の1区33点の「?」に変換されるようにすれば
> よい.
> 2. UCS への変換の場合, 存在する複数のコード変換をサポートするしかない.
> いま分かっている限りでは, Microsoft 式のコード変換と JIS X 0221 式の
> コード変換の少なくとも2つが必要である.

とあるので、Microsoft の API と違う挙動を定義しています。

つまり、この問題は mbstring の SJIS-Win が MS932 のマッピングを
想定していながら SJIS-Open のスキームを一部の記号や
ユーザ定義領域について採用しているという
奇妙なマッピングであるのが発端です。

それは SJIS-Open が SJIS-Win のエイリアスになっていることからも
伺い知れます。

ただ、正しいかどうかは別に、下位互換性が損なわれてしまったのは
問題ですね。

そこで、妥協策として、

1.  新しい (正しい) マッピングを持つコードセット名として
    「SJIS-MS」を新設する。eucJP-MS との対照ともなるので
    名前も「SJIS-Win」と比較してより直感的と思われる。
2.  「SJIS-Win」および「SJIS-Open」に関しては、SJIS-Open の
    コードセットを持つものとして扱う。少なくともこの 2 つの
    コードセット名が指定された場合はこれまでの挙動を維持する。
3.  「Windows-31J」および「MS_Kanji」については
    「SJIS-MS」のエイリアスとする。
4.  「CP932」についての扱いについては次の 2 つの選択肢。
    a. CP932 = MSCP932 とみなし、SJIS-MS のエイリアスとする。
       (私はこれがいいです)
    b. CP932 != MSCP932 とみなし、「MS932」を SJIS-MS の
       エイリアスとして定義する (Sun Java 方式)。
       ほかの CPxxxx が MSCP を基準にしているとすれば
       一貫性が損なわれる危険がある。

を提案したいと思います。


# それにしてもこの辺、ちゃんと検証する時間が (当時) なかったのが
# 悔やまれます。


komura wrote:
> komura です。
> 
> 最近になってから、一部の文字では、文字コード変換を行うと、PHP 5.2.0 以前
> と違いがあることに気がつきました。
> 
> 以下に例を挙げましたが、これは意図された変更なのでしょうか?
> 
>  # "〜"(EUC-JP:0xA1C1, SJIS:0x8160) を UTF-16 に変換
> 
>  $ php-5.2.0    -r 'echo bin2hex( mb_convert_encoding( "\xA1\xC1", "UTF-16", "EUC-JP" ) );'
>  301c
> 
>  $ php-5.2.1RC3 -r 'echo bin2hex( mb_convert_encoding( "\xA1\xC1", "UTF-16", "EUC-JP" ) );'
>  ff5e
> 
>  $ php-5.2.0    -r 'echo bin2hex( mb_convert_encoding( "\x81\x60", "UTF-16", "SJIS" ) );'
>  301c
> 
>  $ php-5.2.1RC3 -r 'echo bin2hex( mb_convert_encoding( "\x81\x60", "UTF-16", "SJIS" ) );'
>  ff5e
> 
> この例で挙げた "〜" は EUC-JP や SJIS の場合、WAVE DASH(U+301C) に変換する
> PHP 5.2.0 以前の方が(調べた限りでは)一般的だと思うのですが、PHP 5.2.1RC1
> 以降では、FULLWIDTH TILDE(U+FF5E) に変換されています。
> 
> この変更は、「[PHP-dev 1345] PHP への CP932 系エンコーディングの追加パッチ」
> が適用されてから発生しています。
> 
> 少し調べてみたのですが、ext/mbstring/libmbfl/filters/unicode_table_jis.h
> で定義されている変換テーブルの変更が影響しているようです。
> 


-- 
小泉 守義 <moriyoshi @ at.wakwak.com>


PHP-dev メーリングリストの案内