Haskell の内部ではUSC4を使用しています。
UTF8 に変換するには以下のテーブルにより行います。
<http://www.ietf.org/rfc/rfc2279.txt> UCS-4 range (hex.) UTF-8 octet sequence (binary) 0000 0000-0000 007F 0xxxxxxx 0000 0080-0000 07FF 110xxxxx 10xxxxxx 0000 0800-0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx 0001 0000-001F FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 0020 0000-03FF FFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 0400 0000-7FFF FFFF 1111110x 10xxxxxx ... 10xxxxxx
xxx は、UCS を2進数にした一部。
0000 0080 以上 0000 07FF 以下の場合の110xxxxx 10xxxxxx は 110B + (上位 5BIt) と 10B + (下位 6BIt)の2文字と言う意味。
- Codec.Binary.UTF8.Stringのencode、decodeについて調べてみました。
import Data.Word (Word8,Word32) import Data.Bits ((.|.),(.&.),shiftL,shiftR) import Data.Char (chr,ord) import Numeric import Debug.Trace main = do print (map (\x->showHex (ord x) "") "あいう0123") -- UCS 4 -- 'あ' 'い' 'う' '0' '1' '2' '3' -- ["3042","3044","3046","30","31","32","33"] print (map (\x->showHex x "") $ encode' "あいう0123") -- UTF8 -- <--- 'あ' ---><--- 'い' ---><--- 'う' ---> '0' '1' '2' '3' -- ["e3","81","82","e3","81","84","e3","81","86","30","31","32","33"] (putStrLn.decode'.encode') "あ" -- decode'を実行する様子 -- 最初のバイトは 1110xxxx なので 0xF をマスク -- c:e3 -->(c .&. f):3 -- acc:3 r:81 --> c1=(shiftL acc 6:c0) .|. (r .&. 0x3f):1) -- 3を左へ6ビットシフトしてc0 -- 次のバイトは10xxxxxx なので 0x3f をマスク --> 1 -- c0 .|. 1 --> c1 (c0 と 1をORしてc1) -- acc:c1 r:82 --> 3042=(shiftL acc 6:3040) .|. (r .&. 0x3f):2) -- c1を左へ6ビットシフトして3040 -- 次のバイトは10xxxxxx なので 0x3f をマスク --> 2 -- 3040 .|. 2 --> 3042 -- 3042 -- => あ -- | Encode a Haskell String to a list of Word8 values, in UTF8 format. encode' :: String -> [Word8] encode' = concatMap (map fromIntegral . go . ord) where go oc | oc <= 0x7f = [oc] -- 0x7f 以下(ASCII)は同じ -- ラテン補助、拡張、・・・ギリシャ文字、アラビア文字 | oc <= 0x7ff = [ 0xc0 + (oc `shiftR` 6) -- 右へ6ビットシフトした残りと 110B の OR , 0x80 + oc .&. 0x3f -- 甲斐6ビットをマスクし 10B と OR ] -- ひらがな、漢字、ハングルなど。Windows IME 文字パッドはここまで | oc <= 0xffff = [ 0xe0 + (oc `shiftR` 12) -- 右へ6ビットシフトして1110B と OR , 0x80 + ((oc `shiftR` 6) .&. 0x3f) -- 右へ6ビットシフトして下位6ビットをマスク -- 10B と OR , 0x80 + oc .&. 0x3f -- 下位6ビットをマスクし 10B と OR ] -- それ以外は4バイト表現。5バイト、6バイト表現の文字には対応していない。 | otherwise = [ 0xf0 + (oc `shiftR` 18) , 0x80 + ((oc `shiftR` 12) .&. 0x3f) , 0x80 + ((oc `shiftR` 6) .&. 0x3f) , 0x80 + oc .&. 0x3f ] -- -- | Decode a UTF8 string packed into a list of Word8 values, directly to String -- decode' :: [Word8] -> String decode' [ ] = "" decode' (c:cs) | c < 0x80 = chr (fromEnum c) : decode' cs -- 0x7f 以下(ASCII)は同じ | c < 0xc0 = replacement_character : decode' cs -- 0x80-0xbf はエラー | c < 0xe0 = multi1 | c < 0xf0 = multi_byte 2 0xf 0x800 -- ひらがな、漢字、ハングルなど。 -- Windows IME 文字パッドはここまで | c < 0xf8 = multi_byte 3 0x7 0x10000 | c < 0xfc = multi_byte 4 0x3 0x200000 | c < 0xfe = multi_byte 5 0x1 0x4000000 | otherwise = replacement_character : decode' cs -- エラー where multi1 = case cs of c1 : ds | c1 .&. 0xc0 == 0x80 -> let d = ((fromEnum c .&. 0x1f) `shiftL` 6) .|. fromEnum (c1 .&. 0x3f) in if d >= 0x000080 then toEnum d : decode' ds else replacement_character : decode' ds _ -> replacement_character : decode' cs -- multi_byte 2 0xf 0x800 -- (ひらがな、漢字のとき) multi_byte :: Int -> Word8 -> Int -> [Char] multi_byte i mask overlong = trace("c:" ++ showHex c "" ++ " -->(c .&. "++ showHex mask ""++"):" ++ showHex (c .&. mask) "") (aux i cs (fromEnum (c .&. mask))) where aux 0 rs acc -- ロジックはエラー検出のためのもの。次の文字へ | overlong <= acc && acc <= 0x10ffff && (acc < 0xd800 || 0xdfff < acc) && (acc < 0xfffe || 0xffff < acc) = trace(showHex acc "")(chr acc : decode' rs) | otherwise = replacement_character : decode' rs -- エラー aux n (r:rs) acc -- 10xxxxxx の部分 | r .&. 0xc0 == 0x80 = trace("acc:" ++ showHex acc "" ++ " r:" ++ showHex r "" ++ " -->" ++ showHex (shiftL acc 6 .|. fromEnum (r .&. 0x3f)) "" ++ "=(shiftL acc 6:" ++ showHex (shiftL acc 6) "" ++ ") .|. (r .&. 0x3f):" ++ showHex (r .&. 0x3f) "" ++ ")" ) (aux (n-1) rs (shiftL acc 6 .|. fromEnum (r .&. 0x3f))) aux _ rs _ = replacement_character : decode' rs -- エラー -- エラー時に置き換える文字 replacement_character :: Char replacement_character = '\xfffd'