aodama.gif(0.97KB) 文字列を UTF-8 に変換

 最近は UTF-8 で記述されたページや UTF-8 で書かれたテキストファイルを読み込めるエディタも増えてきて、 Visual Basic 5.0 で、文字列を UTF-8 にエンコードおよびデコードする処理を作ってみることにしました。

 MSDN ライブラリを検索してみると、どうやら Win32 API の WideCharToMultiByte() と MultiByteToWideChar() でできそうな感じなので、 さっそく書いてみたのですが、dwFlags に CP_UTF8 を渡すと、Windows 95 の環境だと失敗してしまいます(泣)。 おそらく Windows 95 の WideCharToMultiByte() と MultiByteToWideChar() は UTF-8 には対応していないのではないかと思われます。

とりあえず書いてみた UTF-8 変換コード : ダウンロード utf8dead.txt (4.51KB) ※ Windows 95 だと動作しません

 2002 年当時はまだ Win95 を使用していたので(笑)、 RFC2279 を参考にして自分でゴリゴリ変換する関数を作りました。 UTF-8 は Unicode のために作られた規格なので、 相互変換処理は変換テーブルなどを用いなくとも論理演算処理のみで実現できます。

Option Explicit

'===== UTF-8 変換モジュール =====
'(C)2002-2011 ばら
'MAIL : http://www.geocities.co.jp/SilkRoad/4511/mail.htm
'HOME : http://www.geocities.co.jp/SilkRoad/4511/
'
'VB のみで、UTF-8 のエンコード・デコードを実現するモジュールです。
'WideCharToMultiByte() 等は使用しないため、Windows 95 など
'UTF-8 のエンコードに対応していない古い Windows でも使用可能です。
'まあ、処理速度は確実に遅いでしょう(^^;

Private Const ISUTF8_DEFAULT_READSIZE = 256 'IsUtf8 関数内部で使用されます
Private Const ISUTF8_DEFAULT_UTF8COUNT = 2

'----- Utf8Encode 関数 Ver 1.02 -----
'VB の Unicode 文字列を、UTF-8 エンコードします。
'
'引数 strUnicode
'   UTF-8 エンコードの対象となる文字列式を指定します。
'
'引数 bytUtf8()
'   UTF-8 エンコードされたデータを受け取るバイト型動的配列を
'   指定します。配列サイズは関数内で初期化されますので、
'   宣言のみを行い渡して下さい。
'
'引数 blnAddBOM
'   省略可能です。UTF-8 エンコードしたバイト型配列の先頭に、
'   UTF-8 であることを表す 3 バイトの BOM(Byte Order Mark) を
'   付加するか否かを指定します。
'
'   True  - BOM を付加します。
'   False - BOM を付加しません。(規定値)
'
'戻り値
'   関数が成功した場合、bytUtf8() に書き込んだサイズを返します。
'   失敗した場合は、0 が返ります。
'
'Windows 95 の WideCharToMultiByte() では UTF-8 変換ができないため、
'RFC2279 を参考に自分で作ってみました。VB の内部処理に用いられている
'文字列はもともと Unicode なので、UTF-8 との相互変換は簡単にできます。
'Ver 1.02 より BOM の付加も可能になりました。
'
Public Function Utf8Encode _
    (ByRef strUnicode As String, _
     ByRef bytUtf8() As Byte, _
     Optional ByVal blnAddBOM As Boolean = False) As Long

 Dim lngUnicodeLength As Long                                                               'Unicode 文字列の長さを格納する変数
 Dim lngUtf8Size As Long                                                                    'UTF-8 エンコードされたサイズを格納する変数
 Dim lngCharCode As Long                                                                    '1 文字分の文字コードを格納する長整数型変数
 Dim i As Long                                                                              'ループカウンタ
 
    lngUnicodeLength = Len(strUnicode)                                                      'Unicode 文字列長を得る
    If lngUnicodeLength = 0 Then Exit Function                                              '0 文字の場合関数を抜ける
    ReDim bytUtf8((lngUnicodeLength * 3) + IIf(blnAddBOM, 3, 0) - 1)                        'UTF-8 バッファサイズを確保(念のため文字列長の 3 倍)
    
    If blnAddBOM Then                                                                       'BOM を付加する場合
        bytUtf8(0) = &HEF                                                                   '配列先頭 3 バイトに BOM を書き込む
        bytUtf8(1) = &HBB
        bytUtf8(2) = &HBF
        lngUtf8Size = 3                                                                     'UTF-8 書き込みカウンタを 3 にする
    End If
    
    For i = 1 To lngUnicodeLength Step 1                                                    '文字列の終端までループ
        lngCharCode = CLng(AscW(Mid$(strUnicode, i, 1))) And &HFFFF&                        'i 文字目の 1 文字の文字コードを得、正の値に変換
        If lngCharCode <= &H7F& Then                                                        '文字コードが &H7F 以下の場合
            bytUtf8(lngUtf8Size) = CByte(lngCharCode)                                       'ANSI は変換する必要がないのでそのまま代入
            lngUtf8Size = lngUtf8Size + 1                                                   '1 バイト書き込んだので書き込みカウンタに 1 を加算
        ElseIf (lngCharCode >= &H80&) And (lngCharCode <= &H7FF&) Then                      '文字コードが &H80 〜 &H7FF の場合
            '----- UTF-8 エンコード 1 バイト目の処理 -----
            '1.lngCharCode を 6 ビット右シフトし上位ビット(2 〜 5)の値を得る
            '2.上記の処理により得られた値と 11000000(2 進数) との論理和を求める
            bytUtf8(lngUtf8Size) = CByte((lngCharCode \ &H40&) Or &HC0&)                    '得られた値を配列に代入
            '----- UTF-8 エンコード 2 バイト目の処理 -----
            '1.lngCharCode と 00111111(2 進数) との論理積を求め、下位 6 ビットの値を得る
            '2.上記の処理により得られた値と 10000000(2 進数) との論理和を求める
            bytUtf8(lngUtf8Size + 1) = CByte((lngCharCode And &H3F&) Or &H80&)              '得られた値を配列に代入
            lngUtf8Size = lngUtf8Size + 2                                                   '2 バイト書き込んだので書き込みカウンタに 2 を加算
        ElseIf (lngCharCode >= &H800&) And (lngCharCode <= &HFFFF&) Then                    '文字コードが &H800 〜 &HFFF の場合
            '----- UTF-8 エンコード 1 バイト目の処理 -----
            '1.lngCharCode を 12 ビット右シフトし上位ビット(0 〜 4)の値を得る
            '2.上記の処理により得られた値と 11100000(2 進数) との論理和を求める
            bytUtf8(lngUtf8Size) = CByte((lngCharCode \ &H1000&) Or &HE0&)                  '得られた値を配列に代入
            '----- UTF-8 エンコード 2 バイト目の処理 -----
            '1.lngCharCode と 00001111 11000000(2 進数) との論理和を求め、中間 6 ビット以外を 0(2 進数) にする
            '2.上記の処理により得られた値を 6 ビット右シフトし、lngCharCode の中間 6 ビットの値を得る
            '3.上記の処理により得られた値と 10000000(2 進数) との論理和を求める
            bytUtf8(lngUtf8Size + 1) = CByte(((lngCharCode And &HFC0&) \ &H40&) Or &H80&)   '得られた値を配列に代入
            '----- UTF-8 エンコード 3 バイト目の処理 -----
            '1.lngCharCode と 00111111(2 進数) との論理積を求め、下位 6 ビットの値を得る
            '2.上記の処理により得られた値と 10000000(2 進数) との論理和を求める
            bytUtf8(lngUtf8Size + 2) = CByte((lngCharCode And &H3F&) Or &H80&)              '得られた値を配列に代入
            lngUtf8Size = lngUtf8Size + 3                                                   '3 バイト書き込んだので書き込みカウンタに 3 を加算
        End If
    Next i
    
    If lngUtf8Size Then                                                                     'バッファにデータが書き込まれた場合
        ReDim Preserve bytUtf8(lngUtf8Size - 1)                                             'バッファサイズを実際のサイズに削る
        Utf8Encode = lngUtf8Size                                                            'バッファに書き込んだサイズを返す
    End If

End Function

'----- Utf8Decode Ver 1.03 -----
'UTF-8 エンコードされた文字列を、VB 標準の Unicode 文字列に
'デコードします。UCS-2 のみの対応です。
'
'引数 bytUtf8()
'   UTF-8 エンコードされた文字列が格納されているバイト型配列を
'   指定します。
'
'引数 strDefaultChar
'   省略可能です。UTF-8 エンコードされた文字として不正な文字が
'   見つかった場合に、代わりに使用する文字列式を指定します。
'   この引数を省略すると、長さ 0 の文字列が使用されます。
'
'戻り値
'   関数が成功した場合、UTF-8 デコードした文字列が返ります。
'
'UTF-8 エンコード処理を作ったのなら、デコード処理も作らないと
'かっこ悪いので作ってみました。論理演算を文で表すのが難しく、
'計算処理部のコメントは省略しました(^^;
'Ver 1.03 より BOM 付き UTF-8 にも対応しました。
'
Public Function Utf8Decode _
    (ByRef bytUtf8() As Byte, _
     Optional ByRef strDefaultChar As String) As String

 Dim lngUtf8Size As Long                                                                                'UTF-8 文字列配列のサイズを格納する変数
 Dim lngDefaultCharLength As Long                                                                       '代替文字列のサイズを格納する変数
 Dim strBuffer As String                                                                                'Unicode 文字列を格納するバッファ
 Dim lngWriteLength As Long                                                                             '書き込み位置カウンタ
 Dim bytUcs2Char(1) As Byte                                                                             'Unicode(UCS-2) 1 文字分を格納するバイト型配列
 Dim i As Long                                                                                          '読み込み位置カウンタ

    On Error GoTo ExitFunction                                                                          'エラーが発生したら関数を抜ける
    lngUtf8Size = UBound(bytUtf8)                                                                       '配列サイズを得る
    On Error GoTo 0                                                                                     'エラートラップの無効化
    lngDefaultCharLength = Len(strDefaultChar)                                                          '代替文字列の文字列数を得る
    If lngDefaultCharLength Then                                                                        '代替文字列が指定されている場合
        strBuffer = String$((lngUtf8Size + 1) * lngDefaultCharLength, vbNullChar)                       'バッファを配列サイズの代替文字列数倍サイズ確保
    Else                                                                                                '代替文字列が指定されていない場合
        strBuffer = String$(lngUtf8Size + 1, vbNullChar)                                                'バッファを配列サイズと同サイズ確保
    End If
    lngWriteLength = 1                                                                                  '書き込みカウンタは 1 から開始
    
    If lngUtf8Size >= 2 Then                                                                            'UTF-8 文字列配列が 3 バイト以上の場合
        If bytUtf8(0) = &HEF And bytUtf8(1) = &HBB And bytUtf8(2) = &HBF Then i = 3                     'BOM がある場合はスキップし、読み込みカウンタを 3 から開始
    End If
    
    Do While i <= lngUtf8Size                                                                           '配列の終端までループ
        If bytUtf8(i) <= &H7F Then                                                                      'ANSI 標準文字の場合
            Mid(strBuffer, lngWriteLength, 1) = ChrW$(bytUtf8(i))                                       '文字コードを文字列に変換しバッファに書き込む
            lngWriteLength = lngWriteLength + 1                                                         '書き込みカウンタをインクリメント
            i = i + 1                                                                                   '読み込みカウンタをインクリメント
        ElseIf (bytUtf8(i) >= &HC2 And bytUtf8(i) <= &HDF) Then                                         'UTF-8 2 バイトコードの第 1 バイトらしい場合
            If (i + 1) <= lngUtf8Size Then                                                              '配列の終端に達していない場合
                If (bytUtf8(i + 1) >= &H80 And bytUtf8(i + 1) <= &HBF) Then                             '後ろ 1 バイトも UTF-8 のバイト列である場合
                    bytUcs2Char(0) = ((bytUtf8(i) And &H3) * &H40) Or (bytUtf8(i + 1) And &H3F)         'デコード処理(Unicode 上位バイト)
                    bytUcs2Char(1) = (bytUtf8(i) And &H1C) \ &H4                                        'デコード処理(Unicode 下位バイト)
                    Mid(strBuffer, lngWriteLength, 1) = bytUcs2Char                                     'bytUcs2Char バイト配列をバッファに書き込む
                    lngWriteLength = lngWriteLength + 1                                                 '書き込みカウンタをインクリメント
                    i = i + 2                                                                           '読み込みカウンタを 2 増やす
                Else                                                                                    'その他の文字の場合
                    If lngDefaultCharLength Then GoSub SetDefaultChar                                   '代替文字が指定されている場合、SetDefaultChar ラベルに飛ぶ
                    i = i + 1                                                                           '読み込みカウンタをインクリメント
                End If
            Else
                If lngDefaultCharLength Then GoSub SetDefaultChar
                i = i + 1
            End If
        ElseIf (bytUtf8(i) >= &HE0 And bytUtf8(i) <= &HEF) Then                                         'UTF-8 3 バイトコードの第 1 バイトらしい場合
            If (i + 2) <= lngUtf8Size Then                                                              '配列の終端に達していない場合
                If (bytUtf8(i + 1) >= &H80 And bytUtf8(i + 1) <= &HBF) And _
                   (bytUtf8(i + 2) >= &H80 And bytUtf8(i + 2) <= &HBF) Then                             '後ろ 2 バイトも UTF-8 のバイト列である場合
                    bytUcs2Char(0) = ((bytUtf8(i + 1) And &H3) * &H40) Or (bytUtf8(i + 2) And &H3F)     'デコード処理(Unicode 上位バイト)
                    bytUcs2Char(1) = ((bytUtf8(i) And &HF) * &H10) Or ((bytUtf8(i + 1) And &H3C) \ &H4) 'デコード処理(Unicode 下位バイト)
                    Mid(strBuffer, lngWriteLength, 1) = bytUcs2Char                                     'bytUcs2Char バイト配列をバッファに書き込む
                    lngWriteLength = lngWriteLength + 1                                                 '書き込みカウンタをインクリメント
                    i = i + 3                                                                           '読み込みカウンタを 3 増やす
                Else
                    If lngDefaultCharLength Then GoSub SetDefaultChar
                    i = i + 1
                End If
            Else
                If lngDefaultCharLength Then GoSub SetDefaultChar
                i = i + 1
            End If
        Else
            If lngDefaultCharLength Then GoSub SetDefaultChar
            i = i + 1
        End If
    Loop

    Utf8Decode = Left$(strBuffer, lngWriteLength - 1)                                                   'バッファから余分な部分を除いた文字列を戻り値とする

    Exit Function
    
SetDefaultChar:                                                                                         '代替文字セットラベル
    Mid(strBuffer, lngWriteLength, lngDefaultCharLength) = strDefaultChar                               '代替文字をバッファに書き込む
    lngWriteLength = lngWriteLength + lngDefaultCharLength                                              '書き込みカウンタを代替文字数増やす
    Return                                                                                              '復帰

ExitFunction:

End Function

'----- IsUtf8 関数 Ver 1.02 Beta -----
'バイト型配列に格納されている文字列が UTF-8 であるかどうか判別します。
'
'引数 bytArray()
'   文字列が格納されているバイト型配列を指定します。
'
'引数 lngReadSize
'   省略可能です。文字列判別対象とするバイト数を指定します。
'   この引数を省略すると ISUTF8_DEFAULT_READSIZE の値が使用されます。
'   bytArray() のサイズ(要素数)がここで指定されたサイズ以下の場合、
'   bytArray() のサイズに丸められます。数値を大きくすればするほど
'   判別の信頼性は増しますが、処理時間を多く要することになります。
'
'引数 lngUtf8Count
'   省略可能です。ここに指定した値の回数だけ UTF-8 と確定された場合、
'   UTF-8 と判断して関数が True を返すように指定できます。
'   確定回数が指定された値よりも少ない場合は False を返します。
'   判別の信頼性を増すためにも、2 以上の値を推奨します。
'   この引数を省略すると ISUTF8_DEFAULT_UTF8COUNT の値が使用されます。
'
'引数 blnCountStop
'   省略可能です。ここに True を指定すると、lngUtf8Count に指定した
'   値の回数だけUTF-8 と確定された場合、ただちに判別処理を中止し
'   関数が True を返します。厳密に判定したい場合は使用しないでください。
'
'   True  - 指定回数に達したら関数を抜けます。
'   False - 指定回数に達しても判別を継続します。(規定値)
'
'引数 blnTrustBOM
'   省略可能です。ここに True を指定すると、UTF-8 を表す BOM が
'   存在する場合、ただちに関数を抜け無条件で関数が True を返します。
'
'   True  - 無条件で BOM を信用します。
'   False - BOM を無視して判別を行います。(規定値)
'
'戻り値
'   bytArray() に格納されている文字列が UTF-8 であると判別された
'   場合は True を返します。ただし ANSI 1 バイト文字のみの場合や、
'   UTF-8 ではないマルチバイト文字が 1 文字でも含まれていた場合は
'   False を返しますのでご注意ください。
'
'現バージョンでは UTF-8 として正しい範囲の数値であるか否かで
'判別を行っており、Unicode(UCS-2) にデコードした文字列が
'正しい文字列であるか否かの判別は行っておりませんので、β版です(^^;
'
'この関数の作成には、土田 健一さん作のテキストエディタ JVim の
'judge_jcode 関数を一部参考にさせていただきました。
'
'Ken'ichi Tsuchida Home Page : http://www.st.rim.or.jp/~ken_t/
'
Public Function IsUtf8 _
    (ByRef bytArray() As Byte, _
     Optional ByVal lngReadSize As Long = ISUTF8_DEFAULT_READSIZE, _
     Optional ByVal lngUtf8Count As Long = ISUTF8_DEFAULT_UTF8COUNT, _
     Optional ByVal blnCountStop As Boolean = False, _
     Optional ByVal blnTrustBOM As Boolean = False) As Boolean
 
 Dim lngArraySize As Long                                                                       '配列サイズを格納する変数
 Dim lngReadPosition As Long                                                                    '読み込み位置カウンタ
 Dim lngUtf8ByteSize As Long                                                                    'UTF-8 1 文字のバイト数 - 1 を格納する変数
 Dim lngIsUtf8 As Long                                                                          'UTF-8 確定回数カウンタ
 Dim i As Long                                                                                  'ループカウンタ
 
    If lngReadSize = 0 Then GoTo ExitFunction                                                   '読み込みサイズが 0 の場合、関数を抜ける
    If lngUtf8Count = 0 Then GoTo ExitFunction                                                  '指定確定回数が 0 の場合、関数を抜ける
    On Error GoTo ExitFunction                                                                  'エラーをトラップした場合、関数を抜ける
    lngArraySize = UBound(bytArray) + 1                                                         'bytArray() の配列サイズを得る
    On Error GoTo 0                                                                             'エラートラップを無効にする
    If lngReadSize > lngArraySize Then lngReadSize = lngArraySize                               '読み込みサイズが配列サイズより大きい場合、配列サイズに
    If lngUtf8Count > lngArraySize Then lngUtf8Count = lngArraySize                             '指定確定回数が配列サイズより大きい場合、配列サイズに
    
    If lngArraySize >= 3 Then                                                                   '配列サイズが 3 以上の場合
        If bytArray(0) = &HEF And bytArray(1) = &HBB And bytArray(2) = &HBF Then                'BOM が存在する場合
            If blnTrustBOM Then                                                                 'BOM を無条件で信用する場合
                IsUtf8 = True                                                                   'True を返す
                GoTo ExitFunction                                                               '関数を抜ける
            Else                                                                                'BOM を信用しない場合
                If lngArraySize >= 4 Then                                                       '配列サイズが 4 以上の場合
                    lngReadPosition = 3                                                         '読み込みカウンタを 3 にする
                Else                                                                            'BOM しか存在しない場合
                    GoTo ExitFunction                                                           '関数を抜ける
                End If
            End If
        End If
    End If
        
    Do While lngReadPosition < lngReadSize                                                      '指定サイズ分ループ
        If bytArray(lngReadPosition) <= &H7F Then                                               'ANSI 標準文字だった場合
            lngReadPosition = lngReadPosition + 1                                               '読み込みカウンタをインクリメント
        ElseIf bytArray(lngReadPosition) < &HC0 Then                                            '&HC0 より小さい値の場合(UTF-8 ではない)
            Exit Function                                                                       '関数を抜ける
        ElseIf (bytArray(lngReadPosition) >= &HC0) And (bytArray(lngReadPosition) <= &HFD) Then 'UTF-8 の第 1 バイトらしい場合
            If (bytArray(lngReadPosition) And &HFC) = &HFC Then                                 '上位 6 ビットのフラグが立っている場合
                lngUtf8ByteSize = 5                                                             '次 5 バイトも UTF-8 のバイト列かも
            ElseIf (bytArray(lngReadPosition) And &HF8) = &HF8 Then                             '上位 5 ビットのフラグが立っている場合
                lngUtf8ByteSize = 4                                                             '次 4 バイトも UTF-8 のバイト列かも
            ElseIf (bytArray(lngReadPosition) And &HF0) = &HF0 Then                             '上位 4 ビットのフラグが立っている場合
                lngUtf8ByteSize = 3                                                             '次 3 バイトも UTF-8 のバイト列かも
            ElseIf (bytArray(lngReadPosition) And &HE0) = &HE0 Then                             '上位 3 ビットのフラグが立っている場合
                lngUtf8ByteSize = 2                                                             '次 2 バイトも UTF-8 のバイト列かも
            ElseIf (bytArray(lngReadPosition) And &HC0) = &HC0 Then                             '上位 2 ビットのフラグが立っている場合
                lngUtf8ByteSize = 1                                                             '次 1 バイトも UTF-8 のバイト列かも
            End If
            If (lngReadPosition + lngUtf8ByteSize) >= lngReadSize Then Exit Do                  '配列の終端に達している場合、ループを抜ける
            For i = (lngReadPosition + 1) To (lngReadPosition + lngUtf8ByteSize) Step 1         '第 2 バイト以降からループ
                If Not ((bytArray(i) >= &H80) And (bytArray(i) <= &HBF)) Then Exit Function     'UTF-8 の文字列ではない値の場合、関数を抜ける
            Next i
            lngIsUtf8 = lngIsUtf8 + 1                                                           'UTF-8 確定回数をインクリメント
            lngReadPosition = lngReadPosition + lngUtf8ByteSize + 1                             '次回読み込み位置を設定
        Else                                                                                    'その他の場合(&HFF の場合のみ)
            lngReadPosition = lngReadPosition + 1                                               '読み込みカウンタをインクリメント
        End If
        If blnCountStop Then                                                                    '指定回数ストップフラグが指定されている場合
            If lngIsUtf8 = lngUtf8Count Then Exit Do                                            '指定回数に達した場合、ループを抜ける
        End If
    Loop
    
    If lngIsUtf8 >= lngUtf8Count Then IsUtf8 = True                                             'UTF-8 確定回数が指定回数以上の場合、True を返す
    
ExitFunction:

End Function

UTF-8 変換モジュール for VB5 : ダウンロード utf8.lzh (18.5KB)


VBコーナーにもどる   トップページにもどる