Paradigm Shift Design

ISHITOYA Kentaro's blog.

Unicodeの16進表現からUTF-8のバイト列を得るスクリプト

駐:すでにオチが付いております。

悲しいかなPHPには、他の言語で使えるMODULE.JP - 日本語に絡むUnicodeブロックとスクリプト(正規表現)にあるような

\p{InHiragana}

という表現がない*1ので、正規表現で入力されてきたUTF-8文字列がひらがなかどうかを判定するためには、

<?php
function isHiragana($str){
    return
        preg_match('/^(\xe3\x81[\x81-\xbf]|\xe3\x82[\x80-\x9e])+$/',$str);
}

みたいなコードを書かなくてはいけない*2
で、色々なところで文字コード表を参考に書けばいいよみたいなことが書いてあったから、参考にしてisSymbolを書こうと思ったんだけど、「U+2000-U+206F」とか書いてあるわけ。
Unicodeの16進表現をUTF-8のバイト列を得る方法が分からなくて、調べていたんだけれども、どうも探し方が悪いのか、見つからないので自分で書いてみた。
そしたら、ものすごく長くなったので、もっと頭のいい方法があれば*3教えてください。
っつか、こういうコードが書けないのバレバレ。



追記:
この記事を書き終わって5分後、なんとなく思いついて、恐々と書いてみた。

<?php
//U+3042(あ)
var_dump(mb_convert_encoding(pack("H*", "3042"), "UTF-8", "UCS-2"));

でいいんじゃん…orz
「もっと頭のいい方法があれば」とか!
文字コードの勉強になったと思うこととする…

追記2:

php -r 'var_dump(bin2hex(mb_convert_encoding(pack("H*", "ff10"), "UTF-8", "UCS-2")));'

以下追記前に書いたコード

<?php
ini_set("mbstring.language", "Japanese");
ini_set("mbstring.script_encoding", "UTF-8");
ini_set("mbstring.internal_encoding", "UTF-8");

if(isset($_SERVER["argv"][1]) == false){
    echo "pass unicode like U+2000 or just specify charactor\n";
    exit;
}

$unicode = $_SERVER["argv"][1];

$bytes = array();
if(strpos($unicode, "U+") === false ||
   strlen($unicode) % 2 != 0){
    if(mb_strlen($unicode) == 1){
        $t = unistr_to_ords($unicode, "UTF-8");
        $t = $t[0];
        if($t <= 0xFF){
            $unicode = "00" . dechex($t);
        }else if($t <= 0xFFFF){
            $unicode = dechex($t >> 8) . dechex($t & 0xFF);
        }
        $unicode = "U+" . $unicode;
    }else{
        echo "pass unicode like U+2000 or just specify charactor\n";
        exit;
    }
}

for($i = 1; $i < strlen($unicode) / 2; $i++){
    $bytes[] = hexdec(substr($unicode, $i * 2, 2));
}

$fb = $bytes[0];
$sb = $bytes[1];

$utf = array();
if(count($bytes) == 2){
    $t = ($fb << 8) + $sb;
    if($fb == 0 && $sb <= 127){
        // U+0000 - U+007F
        $utf[0] = $fb;
        $utf[1] = $sb;
    }else if($fb >= 0 && $fb <= 7){
        // U+0080 - U+07FF
        $utf[0] = (($t & 1984) >> 6) + 192;
        $utf[1] = ($t & 63) + 128;
    }else if($fb >= 8 && $fb <= 255){
        // U+0800 - U+FFFF
        $utf[0] = (($t & 61440) >> 12) + 224;
        $utf[1] = (($t & 4032) >> 6) + 128;
        $utf[2] = ($t & 63) + 128;
    }
}else if(count($bytes) == 3){
    //U+10000 - U+1FFFFF
    $tb = $bytes[2];
    $t = ($tb << 16) + ($fb << 8) + $sb;
    $utf[0] = (($t & 3670016) >> 19) + 240;
    $utf[1] = (($t & 516096) >> 13) + 128;
    $utf[2] = (($t & 8064) >> 7) + 128;
    $utf[3] = ($t & 127) + 128;
}else{
    echo "pass unicode like U+2000 or just specify charactor\n";
    exit;
}

$utf8_bytes = array();
$utf8_str = "";
foreach($utf as $u){
    $hex = dechex($u);
    $hex = (strlen($hex) == 1) ? "0" . $hex : $hex;
    $utf8_bytes[] = '0x' . strtoupper($hex);
    $utf8_str .= $hex;
}
echo "UTF-8 for Unicode " . $unicode . "(" . pack('H*', $utf8_str) . ") is ";
echo implode(",", $utf8_bytes) . "(" . count($utf8_bytes) . "bytes)\n";

/*
 By Darien Hager, Jan 2007... Use however you wish, but please
 please give credit in source comments.

 Change "UTF-8" to whichever encoding you are expecting to use.
*/

function unistr_to_ords($str, $encoding = 'UTF-8'){
    // Turns a string of unicode characters into an array of ordinal values,
    // Even if some of those characters are multibyte.
    $str = mb_convert_encoding($str,"UCS-4BE",$encoding);
    $ords = array();

    // Visit each unicode character
    for($i = 0; $i < mb_strlen($str,"UCS-4BE"); $i++){
        // Now we have 4 bytes. Find their total
        // numeric value.
        $s2 = mb_substr($str,$i,1,"UCS-4BE");
        $val = unpack("N",$s2);
        $ords[] = $val[1];
    }
    return($ords);
}

で、

[kent@localhost hatena]$ php -f misc/ucsTest.php "U+0061"
UTF-8 for Unicode U+0061(a) is 0x00,0x61(2bytes)
[kent@localhost hatena]$ php -f misc/ucsTest.php "U+0060"
UTF-8 for Unicode U+0060(`) is 0x00,0x60(2bytes)
[kent@localhost hatena]$ php -f misc/ucsTest.php "U+0065"
UTF-8 for Unicode U+0065(e) is 0x00,0x65(2bytes)
[kent@localhost hatena]$ php -f misc/ucsTest.php "カ"
UTF-8 for Unicode U+ff76(カ) is 0xEF,0xBD,0xB6(3bytes)
[kent@localhost hatena]$ php -f misc/ucsTest.php "あ"
UTF-8 for Unicode U+3042(あ) is 0xE3,0x81,0x82(3bytes)
[kent@localhost hatena]$ php -f misc/ucsTest.php "い"
UTF-8 for Unicode U+3044(い) is 0xE3,0x81,0x84(3bytes)

って使います。

参考

*1:みたいな

*2:らしい

*3:絶対あるハズ