UTF-8テキストデータの書き出し

UTF-8コードは今後のWindows上のテキストファイルの「標準」になるようです。
例えば「メモ帳」です。   Windows以前からMicrosoftのテキストデータの「標準」は「Ascii(シフトJIS漢字)コード」でした。
Windows10になってからも例えば「メモ帳」で新規に作成するテキストファイルのデフォルトは「Ascii(シフトJIS漢字)コード」でした。
ですが、Windows10の「ビルド1903」になってこれが変更され、「UTF-8コード」がデフォルトに変更されました。
さらにステータスバーの右方には改行コード、文字コードが表示されるようになり、名前を付けて保存のダイアログでは、



このように文字コードの指定ができるようになりました。
ここで「ANSI」を指定すれば従来の「Ascii(シフトJIS漢字)コード」になりますが、デフォルトは「UTF-8」です。



しかし、VBAでは標準的なテキストファイル、CSVファイルの読み書きの方法では「UTF-8コード」は扱えないので、 本ページの方法が必要になります。
起動画面です。


(この画像をクリックすると、このページのサンプルがダウンロードができます。)
ダウンロードして解凍させると、2つのワークブックが作成されます。
05_WriteUTF8TextFile1.xlsm」は「UTF-8(BOM付き)」、 「05_WriteUTF8TextFile2.xlsm」は「UTF-8(BOM無し)」となります。
どちらも「UTF-8テキストファイル書き出し」ボタンをクリックすると、「名前を付けて保存」ダイアログが表示されるので、 ここで出力先フォルダ及びファイル名を指定して「保存」ボタンのクリックで出力されます。 サンプルのままだと3行のテキストが出力されます。

バイナリエディタの比較画面です。


(この画像をクリックすると、大きいイメージが表示されます。)
まず「BOMって何?」ということになると思います。 私も良く解っていなかったのでバイナリエディタで内容を比較してみました。



「バイナリエディタ」というのは昔の人でないと馴染みがないかも知れませんがいわゆる「16進ダンプ」です。 画面上に「文字」として表示されているものがファイルデータでどのように扱われているかを見るもので、この画像は「FavBinEdit」というフリーソフトですが、 他の製品でも画面表現はほぼ似たようなものになります。



左の「Address」は16進値です。ですから16バイトで1行進みます。
16バイトを「00」から「0F」として横並びの見出しが表示されており、 そのバイト位置のデータの16進値が表示されています。
さらに右端の「0」~「F」の見出しのところには文字として表示される値が表示されます。



これらは上の「05_WriteUTF8TextFile1.xlsm」「05_WriteUTF8TextFile2.xlsm」で出力されたものを 「バイナリエディタ」に取り込んで表示させたものです。


上半分が「BOM付き」です。 見比べると判るように先頭3バイト分に余分な制御コードが付いていて、実際に表示されるデータが4バイト目(0始まりだから「3」の位置)からになっています。
この先頭3バイトの「EF-BB-BF」が「BOM」です。 実際には「Byte Order Mark」というらしく、データの文字コードなどの制御情報だということです。
「メモ帳」でもこの区別があるわけですから一般の利用者は混乱してしまうでしょうが、単に「このような種類がある」という理解で良いと思います。 作成したデータファイルを他のシステムに引き渡すような用途の場合は受け取り側のシステムに「この文字コードでないとダメ」という制限があることがあるので データファイルの作成時に注意が必要です。



なお、この「バイナリエディタ」の画像では半角英字のデータのみなのですが、UTF-8コードでは全角文字は1文字が3バイト使われているのが判るので、 関心がある方は「バイナリエディタ」はほとんどフリーソフトなので確認してみて下さい。

ソースコードです。
まずは「BOM付き」バージョンです。(05_WriteUTF8TextFile1.xlsm)


'***************************************************************************************************
'   UTF-8テキストファイル書き出しサンプル(BOM付き)                  Module1(Module)
'
'   作成者:井上治  URL:https://www.ne.jp/asahi/excel/inoue/ [Excelでお仕事!]
'***************************************************************************************************
'   [参照設定]
'   ・Microsoft ActiveX Data Objects 2.8 Library
'***************************************************************************************************
'変更日付 Rev  変更履歴内容------------------------------------------------------------------------>
'20/04/26(1.00)新規作成
'***************************************************************************************************
Option Explicit
'===================================================================================================
Private Const g_cnsTitle As String = "UTF-8テキストファイル書き出し"
Private Const g_cnsFilter As String = "テキストファイル (*.txt;*.dat),*.txt;*.dat"
Private Const g_cnsCharset As String = "UTF-8"

'***************************************************************************************************
'   ■■■ ワークシート側からの呼び出し処理 ■■■
'***************************************************************************************************
'* 処理名 :WRITE_TextFile1
'* 機能  :テキストファイル書き出しサンプル(UTF-8、BOM付き)
'---------------------------------------------------------------------------------------------------
'* 返り値 :(なし)
'* 引数  :(なし)
'---------------------------------------------------------------------------------------------------
'* 作成日 :2020年04月26日
'* 作成者 :井上 治
'* 更新日 :2020年04月26日
'* 更新者 :井上 治
'* 機能説明:
'* 注意事項:サンプルなのでエラー処理は行なっていません
'***************************************************************************************************
Sub WRITE_TextFile1()
    '-----------------------------------------------------------------------------------------------
    Dim objAdost As ADODB.Stream                                    ' 入力ファイル
    Dim lngRow As Long                                              ' 収容するセルの行
    Dim lngRowMax As Long                                           ' データが収容された最終行
    Dim lngRec As Long                                              ' レコード件数カウンタ
    Dim strRec As String                                            ' 書き出すレコード内容
    Dim strFileName As String                                       ' OPENするファイル名(フルパス)
    Dim vntFileName As Variant                                      ' ファイル名受取り用
    '-----------------------------------------------------------------
    ' ①「名前を付けて保存」のフォームでファイル名の指定を受ける
    Application.StatusBar = "出力するファイル名を指定して下さい。"
    vntFileName = Application.GetSaveAsFilename(InitialFilename:="SAMPLE.txt", _
                                                FileFilter:=g_cnsFilter, _
                                                Title:=g_cnsTitle)
    ' キャンセルされた場合はFalseが返るので以降の処理は行なわない
    If VarType(vntFileName) = vbBoolean Then Exit Sub
    strFileName = vntFileName
    '-----------------------------------------------------------------
    ' ②収容最終行の判定(Excel認知の最終行から上に向かってデータがある行を探す)
    If ActiveSheet.FilterMode Then ActiveSheet.ShowAllData      ' オートフィルタ解除
    lngRowMax = Range("$A$" & Rows.Count).End(xlUp).Row
    ' データ未登録は終了
    If lngRowMax < 2 Then
        Application.StatusBar = False
        MsgBox "テキストをA列2行目から入力してから起動して下さい。", vbExclamation, g_cnsTitle
        Exit Sub
    End If
    ' 2行目から開始
    lngRow = 2
    '-----------------------------------------------------------------
    ' ③指定ファイルをOPEN
    Set objAdost = New ADODB.Stream
    ' ADODB.Stream処理
    With objAdost
        .Type = adTypeText
        ' 文字コード、改行コード指定
        .Charset = g_cnsCharset                                 ' UTF-8コード指定
        .LineSeparator = adCRLF                                 ' 改行コード指定(CRLF)
        '.LineSeparator = adLF                                   ' 改行コード指定(LF)
        '.LineSeparator = adCR                                   ' 改行コード指定(CR)
        .Open
        '-------------------------------------------------------------
        ' ④最終行まで繰り返す
        Do While lngRow <= lngRowMax
            ' レコード件数カウンタの加算
            lngRec = lngRec + 1
            Application.StatusBar = "出力中です....(" & lngRec & "レコード目)"
            ' A列内容をレコードにセット(先頭は2行目)
            strRec = Cells(lngRow, 1).Value
            ' レコードを出力
            .WriteText strRec, adWriteLine
            ' 行を加算
            lngRow = lngRow + 1
        Loop
        '-------------------------------------------------------------
        ' ⑤指定ファイルをCLOSE
        .SetEOS
        .SaveToFile strFileName, adSaveCreateOverWrite
        .Close
    End With
    Set objAdost = Nothing
    Application.StatusBar = False
    ' 終了の表示
    MsgBox "ファイル出力が完了しました。" & vbCr & _
        "レコード件数=" & lngRec & "件", vbInformation, g_cnsTitle
End Sub

'----------------------------------------<< End of Source >>----------------------------------------
このページより前に紹介していた「FileSystemObject」ではなく、このように「ADODB.Stream」での処理になります。
05_WriteUTF8TextFile1.xlsm」では単に「BOM」は意識せずに出力する記述ですが、 この方法だと「BOM付き」になります。
「文字コード」「改行コード」はADODB.Stream処理の先頭で指定できますが、 「BOM」の有無はここで指定するものではありません。

続いて「BOM無し」バージョンです。(05_WriteUTF8TextFile2.xlsm)


'***************************************************************************************************
'   UTF-8テキストファイル書き出しサンプル(BOM無し対応版)            Module2(Module)
'
'   作成者:井上治  URL:https://www.ne.jp/asahi/excel/inoue/ [Excelでお仕事!]
'***************************************************************************************************
'   [参照設定]
'   ・Microsoft ActiveX Data Objects 2.8 Library
'***************************************************************************************************
'変更日付 Rev  変更履歴内容------------------------------------------------------------------------>
'20/04/26(1.00)新規作成
'***************************************************************************************************
Option Explicit
'===================================================================================================
Private Const g_cnsTitle As String = "UTF-8テキストファイル書き出し"
Private Const g_cnsFilter As String = "テキストファイル (*.txt;*.dat),*.txt;*.dat"
Private Const g_cnsCharset As String = "UTF-8"

'***************************************************************************************************
'   ■■■ ワークシート側からの呼び出し処理 ■■■
'***************************************************************************************************
'* 処理名 :WRITE_TextFile1
'* 機能  :テキストファイル書き出しサンプル(UTF-8、BOM無し)
'---------------------------------------------------------------------------------------------------
'* 返り値 :(なし)
'* 引数  :(なし)
'---------------------------------------------------------------------------------------------------
'* 作成日 :2020年04月26日
'* 作成者 :井上 治
'* 更新日 :2020年04月26日
'* 更新者 :井上 治
'* 機能説明:
'* 注意事項:サンプルなのでエラー処理は行なっていません
'***************************************************************************************************
Sub WRITE_TextFile1()
    '-----------------------------------------------------------------------------------------------
    Dim objAdost As ADODB.Stream                                    ' 入力ファイル
    Dim lngRow As Long                                              ' 収容するセルの行
    Dim lngRowMax As Long                                           ' データが収容された最終行
    Dim lngRec As Long                                              ' レコード件数カウンタ
    Dim strRec As String                                            ' 書き出すレコード内容
    Dim strFileName As String                                       ' OPENするファイル名(フルパス)
    Dim vntFileName As Variant                                      ' ファイル名受取り用
    Dim tblByte() As Byte                                           ' Byteテーブル(一時変換用)
    '-----------------------------------------------------------------
    ' ①「名前を付けて保存」のフォームでファイル名の指定を受ける
    Application.StatusBar = "出力するファイル名を指定して下さい。"
    vntFileName = Application.GetSaveAsFilename(InitialFilename:="SAMPLE.txt", _
                                                FileFilter:=g_cnsFilter, _
                                                Title:=g_cnsTitle)
    ' キャンセルされた場合はFalseが返るので以降の処理は行なわない
    If VarType(vntFileName) = vbBoolean Then Exit Sub
    strFileName = vntFileName
    '-----------------------------------------------------------------
    ' ②収容最終行の判定(Excel認知の最終行から上に向かってデータがある行を探す)
    If ActiveSheet.FilterMode Then ActiveSheet.ShowAllData      ' オートフィルタ解除
    lngRowMax = Range("$A$" & Rows.Count).End(xlUp).Row
    ' データ未登録は終了
    If lngRowMax < 2 Then
        Application.StatusBar = False
        MsgBox "テキストをA列2行目から入力してから起動して下さい。", vbExclamation, g_cnsTitle
        Exit Sub
    End If
    ' 2行目から開始
    lngRow = 2
    '-----------------------------------------------------------------
    ' ③指定ファイルをOPEN
    Set objAdost = New ADODB.Stream
    ' ADODB.Stream処理
    With objAdost
        .Type = adTypeText
        ' 文字コード、改行コード指定
        .Charset = g_cnsCharset                                 ' UTF-8コード指定
        .LineSeparator = adCRLF                                 ' 改行コード指定(CRLF)
        '.LineSeparator = adLF                                   ' 改行コード指定(LF)
        '.LineSeparator = adCR                                   ' 改行コード指定(CR)
        .Open
        '-------------------------------------------------------------
        ' ④最終行まで繰り返す
        Do While lngRow <= lngRowMax
            ' レコード件数カウンタの加算
            lngRec = lngRec + 1
            Application.StatusBar = "出力中です....(" & lngRec & "レコード目)"
            ' A列内容をレコードにセット(先頭は2行目)
            strRec = Cells(lngRow, 1).Value
            ' レコードを出力
            .WriteText strRec, adWriteLine
            ' 行を加算
            lngRow = lngRow + 1
        Loop
        '-------------------------------------------------------------
        ' ⑤BOM無しに変換(4バイト目を先頭に移動)
        .Position = 0                                           ' Stream位置を0にする
        .Type = adTypeBinary                                    ' バイナリモードに変更
        .Position = 3                                           ' Stream位置を3にする
        tblByte = .Read                                         ' Stream内容をByteテーブルに格納
        .Close                                                  ' 一旦閉じる
        .Open                                                   ' 再Open
        .Write tblByte                                          ' Byteテーブルを書き出す
        '-------------------------------------------------------------
        ' ⑥指定ファイルをCLOSE
        .SetEOS
        .SaveToFile strFileName, adSaveCreateOverWrite
        .Close
    End With
    Set objAdost = Nothing
    Application.StatusBar = False
    ' 終了の表示
    MsgBox "ファイル出力が完了しました。" & vbCr & _
        "レコード件数=" & lngRec & "件", vbInformation, g_cnsTitle
End Sub

'----------------------------------------<< End of Source >>----------------------------------------
こちらが「BOM無し」です。
1つ上のソースコードと比べると、⑤にあったファイルCloseの記述が⑥に移動し、 ⑤の「BOM無しに変換」が加わります。
一旦「BOM付き」で出力されたものからバイナリ処理で4バイト目以降をバイト配列に退避させてこれを先頭位置から書き出させることで、 「BOM」の3バイト分を除去させています。



近年ではWeb系アプリが「UTF-8(BOM無し)」を基本としていることが多いようなので、 この方法は知っておいた方が良いと思います。