uniord()

unichr() があるなら uniord() も欲しいよね、ってことで書いてみました。


unichr.xml の extension 要素に追加する関数定義:

<function name="uniord">
	<proto>int uniord(string str)</proto>
	<description><![CDATA[
		Get the code point which corresponds to the given UTF-8 encoded string.
	]]></description>
	<code><![CDATA[
if (str_len == 0) {
	RETURN_FALSE;
}
long cp = 0;
if ((str[0] & 0xE0) == 0xE0) {
	if (str_len < 3) {
		RETURN_FALSE;
	}
	cp = ((long)(str[0] & 0x1F)) << 12;
	cp |= ((long)(str[1] & 0x7F)) << 6;
	cp |= (long)(str[2] & 0x7F);
} else if ((str[0] & 0xC0) == 0xC0) {
	if (str_len < 2) {
		RETURN_FALSE;
	}
	cp = ((long)(str[0] & 0x3F)) << 6;
	cp |= (long)(str[1] & 0x7F);
} else {
	if (str[0] & 0x80) {
		RETURN_FALSE;
	}
	cp = (long)str[0];
}
RETURN_LONG(cp);
	]]></code>
</function>

unichr() と同じく UCS-2 のみ対応で、また最低限の長さと第 1 バイトのチェックさえ通れば UTF-8 として不正なシーケンスでもエラーとならない問題があります。


テスト用コード:

<?php
dl('unichr.so');
for ($i = 0; $i < 65536; $i++) {
    if ($i != uniord(unichr($i))) {
        $str = unichr($i);
        $fmt = '0x%02X';
        for ($j = 1; $j < strlen($str); $j++) {
            $fmt .= ' 0x%02X';
        }
        $dump = vsprintf($fmt, unpack('C*', $str));
        fprintf(STDERR, "failed at U+%04X (%s).?n", $i, $dump);
        exit(1);
    }
}
fwrite(STDERR, "success.?n");
exit(0);
?>


余談になりますが unicode 機能を有効にした PHP6 の chr() / ord() では内部で ICU の関数を呼び出して Unicode 文字とコードポイントの変換ができるようです。(ext/standard/string.c)