VBAで Open 文と Line Input # を使ってCSVを読むと、日本語が「譌・譛ャ」のような意味の通らない文字に変わることがあります。英数字は正しく読めるのに、日本語だけが化けるのが典型的な症状です。
多くの場合、原因はUTF-8で保存されたCSVを、Shift_JISとして読んでいることです。この記事では、原因の確かめ方、BOMの有無の見分け方、ADODB.Stream で文字コードを指定して読む手順、読み込んだ後の確認方法を説明します。
なぜOpen文で読むと文字化けするのか
VBAの文字列は、内部ではUnicodeで扱われます。Line Input # でファイルを読むとき、VBAはファイルのバイト列を、Windowsのシステム既定の文字コードとして解釈して文字列に変換します。日本語版Windowsでは、この既定の文字コードは通常Shift_JIS系(CP932)です。
Open 文には、文字コードを指定する引数がありません。そのため、次のように結果が分かれます。
| CSVの文字コード | Open文 + Line Inputで読んだ結果 |
|---|---|
| Shift_JIS(CP932) | 正しく読める |
| UTF-8(BOMなし) | 日本語が文字化けする |
| UTF-8(BOM付き) | 日本語が文字化けし、1行目の先頭にも余分な文字が付く |
なお、Windowsの地域設定で「ワールドワイド言語サポートで Unicode UTF-8 を使用」(ベータ版の設定)が有効になっていると、既定の文字コードが変わります。この場合、同じマクロでもPCによって結果が変わることがあります。ある人のPCでは読めるのに別の人のPCでは化ける、という場合は、この設定の違いも疑ってください。
症状から状態を見分ける
| 症状 | 考えられる状態 |
|---|---|
| 日本語が、漢字と半角カナの混ざった別の文字になる | UTF-8のファイルをShift_JISとして読んでいる |
| 英数字は正しく、日本語だけが化ける | 同上(英数字はどちらの文字コードでも同じバイトになる) |
| 1列目の見出しだけが比較で一致しない | 先頭のBOMが文字として残っている |
| 文字コードを指定して読んだら、置換文字(U+FFFD、ひし形に?の記号)が出る | Shift_JISのファイルをUTF-8として読んでいる |
BOMの有無を確かめる
BOMは、UTF-8のファイルの先頭に付くことがある EF BB BF の3バイトです。BOMがあれば、そのファイルはUTF-8だとほぼ判断できます。ただし、BOMがない場合は、UTF-8(BOMなし)なのかShift_JISなのかを、BOMだけでは判断できません。
先頭の3バイトを調べる関数の例です。
Function HasUtf8Bom(ByVal filePath As String) As Boolean
Dim fileNo As Integer
Dim b(0 To 2) As Byte
fileNo = FreeFile
Open filePath For Binary Access Read As #fileNo
If LOF(fileNo) >= 3 Then
Get #fileNo, 1, b
HasUtf8Bom = (b(0) = &HEF And b(1) = &HBB And b(2) = &HBF)
End If
Close #fileNo
End Function
BOMがない場合は、次の方法で文字コードを確かめます。
- CSVを出力したシステムの仕様や設定画面を見る:出力時の文字コードが選べる場合や、仕様書に書かれている場合があります。
- テキストエディタで開く:メモ帳や多くのテキストエディタでは、画面下部などに文字コードが表示されます。表示される場所や表記はエディタやバージョンによって異なります。
- 両方の文字コードで読んでみる:日本語が正しく表示されるほうを採用します。ただし、この方法で判定を自動化すると誤判定することがあるため、出力元の仕様が分かるならそちらを優先します。
ADODB.Streamで文字コードを指定して読む
ADODB.Stream を使うと、文字コード(Charset)を指定してファイルを読めます。CreateObject で作れば、参照設定は不要です。なお、ADODBはWindows版Excelの機能で、Mac版Excelでは使えません。
Function ReadTextFile(ByVal filePath As String, ByVal charsetName As String) As String
Dim stm As Object
Set stm = CreateObject("ADODB.Stream")
With stm
.Type = 2 ' テキストとして扱う
.Charset = charsetName ' "UTF-8" または "Shift_JIS"
.Open
.LoadFromFile filePath
ReadTextFile = .ReadText(-1) ' -1 で全体を読む
.Close
End With
End Function
UTF-8を指定して読むと、先頭のBOMは通常取り除かれます。ただ、もし残っていると1列目の見出しの比較がずれるため、読み込んだ後に念のため次の1行を入れておきます。
If Left(allText, 1) = ChrW(&HFEFF) Then allText = Mid(allText, 2)
読み込んでシートに書き出す例
BOMがあればUTF-8で読み、なければ定数で指定した文字コードで読みます。そのうえで、行とカンマで分けて「取込」シートへ書き出す例です。実行する前に、「取込」という名前のシートを作っておいてください。このマクロは「取込」シートの内容をすべて消してから書き出します。
Option Explicit
Const DEFAULT_CHARSET As String = "UTF-8" ' BOMがないときの文字コード。Shift_JISなら "Shift_JIS"
Sub ImportCsvWithCharset()
Dim filePath As Variant
Dim charsetName As String
Dim allText As String
Dim lines As Variant
Dim fields As Variant
Dim ws As Worksheet
Dim i As Long, j As Long
filePath = Application.GetOpenFilename("CSVファイル (*.csv),*.csv")
If VarType(filePath) = vbBoolean Then Exit Sub
If HasUtf8Bom(CStr(filePath)) Then
charsetName = "UTF-8"
Else
charsetName = DEFAULT_CHARSET
End If
allText = ReadTextFile(CStr(filePath), charsetName)
If Left(allText, 1) = ChrW(&HFEFF) Then allText = Mid(allText, 2)
If Len(allText) = 0 Then
MsgBox "ファイルが空です。"
Exit Sub
End If
' 文字コードが合っていないと置換文字が入ることがある
If InStr(allText, ChrW(&HFFFD)) > 0 Then
MsgBox "置換文字が含まれています。文字コードの指定(" & charsetName & ")を確認してください。"
Exit Sub
End If
' 改行をLFにそろえ、末尾の改行を1つ取り除いてから行に分ける
allText = Replace(allText, vbCrLf, vbLf)
If Right(allText, 1) = vbLf Then allText = Left(allText, Len(allText) - 1)
lines = Split(allText, vbLf)
Set ws = ThisWorkbook.Worksheets("取込")
ws.Cells.Clear
ws.Cells.NumberFormat = "@" ' 先頭の0が消えないよう文字列で書き込む
For i = 0 To UBound(lines)
fields = Split(lines(i), ",")
For j = 0 To UBound(fields)
ws.Cells(i + 1, j + 1).Value = fields(j)
Next j
Next i
MsgBox "文字コード:" & charsetName & vbCrLf & _
"先頭行:" & lines(0) & vbCrLf & _
"データ件数(見出しを除く):" & UBound(lines)
End Sub
この例は、文字化けを解消する部分に絞った最小限の処理です。次のような場合は、この分け方では行や列がずれるため、別の処理が必要です。
- 値の中にカンマがあり、ダブルクォーテーションで囲まれている
- セル内改行を含む
- 改行がCRだけのファイル
また、1セルずつ書き込んでいるため、数万行を超えるファイルでは時間がかかります。
読み込んだ後に先頭行と件数を確かめる
文字化けが直ったように見えても、次の点を確認してから後続の処理に進みます。
- 先頭行(見出し)が想定どおりか:1列目の見出しを、期待する列名と文字列で比べます。BOMが残っていると、見た目は同じでも一致しません。
If Split(lines(0), ",")(0) <> "顧客コード" Then
MsgBox "1列目の見出しが想定と違います:" & Split(lines(0), ",")(0)
End If
- 件数が元のデータと合っているか:
UBound(lines)は、見出しを除いたデータの件数です(見出し行がある場合)。CSVを出力したシステムの件数と比べます。末尾に空行が複数あると、その分だけ多く数えられます。 - 日本語を含む行を目で確認する:先頭の数行だけでなく、最終行や途中の行も見ます。一部の行だけ化けている場合は、ファイルの途中で文字コードが変わっている(別のファイルを連結しているなど)可能性があります。
- 置換文字が出ていないか:上のコードのように
ChrW(&HFFFD)を検索します。含まれていれば、指定した文字コードがファイルと合っていない可能性が高いです。
Shift_JISのファイルも混ざる場合
取り込むCSVに、UTF-8とShift_JISの両方が混ざることがあります。BOM付きのUTF-8とShift_JISだけなら、HasUtf8Bom の結果で読み分けられます。BOMなしのUTF-8も混ざる場合は、ファイル名やフォルダで出力元を区別し、出力元ごとに文字コードを決めておくほうが確実です。出力元のシステムで文字コードを選べるなら、どちらかにそろえてもらうのがいちばん簡単です。
