【入門編】【実務中級者向け】FSOによる複数CSV走査時の「BOM(Byte Order Mark)付きテキスト」の先頭余分文字スキップと正常パース – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク
スポンサーリンク

CSVの先頭に潜む「見えない幽霊」を退治せよ:BOM付きUTF-8を完璧に制御する極限の知見

こんにちは。現場の自動化を支えるエンジニアの皆さん。今日もコードと向き合っていることと思います。

「昨日まで動いていたスクリプトが、新しいCSVファイルを読み込んだ途端にエラーを吐き出した……」
「CSVの1行目の項目名が、なぜか変な記号(`` など)から始まっていて、VLOOKUPや結合処理が失敗する……」

もしあなたがそんな壁にぶつかっているなら、おめでとうございます。あなたは今、「文字コードとエンコーディング」という、プログラミングにおけるもっとも深淵で、もっとも美しい領域の入り口に立っています。

今回は、VBScriptの標準的な武器である「FileSystemObject (FSO)」を使いながら、実務で必ず遭遇する「BOM(Byte Order Mark)付きテキスト」の正体を暴き、それをスマートに処理する技術を伝授しましょう。

ここをクリアすれば、あなたのVBScriptは「単なるマクロ」から「堅牢なシステム」へと進化しますよ。

1. 「見えない幽霊」BOMの正体を暴く

まず、敵を知ることから始めましょう。

CSVファイルをテキストエディタ(メモ帳など)で開くと、一見ただのテキストに見えます。しかし、コンピュータの視点では、ファイルの先頭に「このファイルはUTF-8ですよ!」という合図(署名)が書き込まれていることがあります。これが BOM (Byte Order Mark) です。

BOMの正体は「3バイトの魔物」

UTF-8の場合、BOMはバイナリレベルで見ると `0xEF, 0xBB, 0xBF` という3バイトのデータです。

問題は、VBScriptの `FileSystemObject` (FSO) です。FSOの `OpenTextFile` メソッドは、基本的にシステムのデフォルトエンコーディング(日本語環境ならShift-JIS / ANSI)としてファイルを読み込もうとします。

すると、FSOはこう誤解してしまうのです。
> 「おや? 先頭に `0xEF`, `0xBB`, `0xBF` という3つの文字があるぞ。よし、文字として読み込もう!」

その結果、プログラムが読み込んだ文字列の先頭には、見たこともないゴミ文字(`` など)が混入してしまいます。これが、データ照合を失敗させる「幽霊」の正体です。

2. 実践:FSOでBOMを検知し、スキップする戦略

「FSOを使わずに、もっと新しいライブラリを使えばいいじゃないか」と思うかもしれません。確かにその通りです。しかし、現場では「既存のFSOベースの資産を活かしつつ、最小限の改修で対応せよ」というミッションが下されることも珍しくありません。

ここでは、FSOを使いながら「先頭のゴミを判定して取り除く」実務的なアプローチを解説します。

戦略図

1. バイナリではなく、一度「文字列」として読み込む。
2. 先頭の文字が「BOMの文字」であるかを判定する。
3. BOMだった場合は、その文字を切り捨てて、純粋なデータだけを抽出する。

【コード例】BOMを検知して正常にパースする関数

' =================================================================
' 目的: BOM付きUTF-8のCSVファイルを、BOMを考慮して安全に読み込む
' ターゲット: 実務レベルのファイル走査スクリプト
' =================================================================

Option Explicit

Dim fso, targetFile, content
Set fso = CreateObject("Scripting.FileSystemObject")

' テスト用のファイルパス(実際の環境に合わせて変更してください)
targetFile = "C:\temp\sample_utf8.csv"

' 実行例
content = ReadFileWithBomHandling(fso, targetFile)

If content <> "" Then
    WScript.Echo "--- 読み込み成功 ---"
    WScript.Echo content
Else
    WScript.Echo "ファイルが空、または読み込みに失敗しました。"
End If

' -----------------------------------------------------------------
' 関数: ReadFileWithBomHandling
' 引数: objFSO (FileSystemObject), strPath (ファイルパス)
' 戻り値: BOMを除去したファイル内容(String)
' -----------------------------------------------------------------
Function ReadFileWithBomHandling(objFSO, strPath)
    Dim objStream, strText, strFirstChar
    
    ' ファイルの存在確認(エラー回避の基本)
    If Not objFSO.FileExists(strPath) Then
        WScript.Echo "Error: File not found -> " & strPath
        ReadFileWithBomHandling = ""
        Exit Function
    End If

    ' FSOのTextStreamを使って読み込む
    ' ※UTF-8を直接指定できないため、一旦ANSIとして読み込む
    Dim objFile
    Set objFile = objFSO.OpenTextFile(strPath, 1) ' 1 = ForReading
    strText = objFile.ReadAll
    objFile.Close

    ' --- ここからがアーキテクトの知恵 ---
    
    ' UTF-8のBOMは、ANSIとして読み込むと ChrW(65279) になる
    ' (Unicodeの Zero Width No-Break Space)
    strFirstChar = Left(strText, 1)
    
    If strFirstChar = ChrW(65279) Then
        ' BOMが見つかった場合、先頭の1文字をスキップして残りを返す
        strText = Mid(strText, 2)
        ' デバッグ用にログを出しておくと、現場での調査が捗ります
        ' WScript.Echo "Log: BOM detected and removed."
    End If
    
    ' ----------------------------------

    ReadFileWithBomHandling = strText
End Function

コードの深掘り解説

# ① `ChrW(65279)` という魔法の数字

ここが最大のポイントです。UTF-8のBOM(`EF BB BF`)を、VBScript(ANSIモード)が読み込むと、内部的に Unicodeの `U+FEFF` という文字として解釈されます。これをVBScriptで表現するのが `ChrW(65279)` です。これを使うことで、「文字として現れたBOM」をピンポイントで特定できます。

# ② `Mid(strText, 2)` による切り出し

BOMが見つかった場合、文字列の2文字目以降を「正解のデータ」として取り出します。これにより、後続の `Split(strText, “,”)` などのパース処理が、ゴミに邪魔されることなく完璧に動作するようになります。

3. さらなる高みへ:プロフェッショナルが選ぶ「ADODB.Stream」

もしあなたが「FSOの制約に縛られたくない」「もっと大規模で複雑なエンコーディングを扱いたい」と考えるなら、`ADODB.Stream` という強力な武器を手に取るべきです。

FSOは「ファイルシステム(箱)」を扱うのには長けていますが、「文字コード(中身)」を扱う能力は限定的です。一方、`ADODB.Stream` は、最初からエンコーディングを意識して設計されたオブジェクトです。

ADODB.Stream を使ったスマートな解決策

' ADODB.Streamを使えば、BOMは「自動的に」処理される
Function ReadFileWithADODB(strPath)
    Dim objStream
    Set objStream = CreateObject("ADODB.Stream")
    
    objStream.Type = 2 ' adTypeText (テキストモード)
    objStream.Charset = "UTF-8" ' ここでUTF-8を指定!
    objStream.Open
    objStream.LoadFromFile strPath
    
    ' UTF-8を指定してOpenすると、ADODB.StreamはBOMを賢く読み飛ばしてくれます
    ReadFileWithADODB = objStream.ReadText
    
    objStream.Close
    Set objStream = Nothing
End Function

「どちらを使うべきか?」

  • FSO: 既存のコードがFSOで組まれており、軽微な修正で済ませたい場合。
  • ADODB.Stream: これから新規に堅牢な読み込み機能を構築する場合。あるいは、UTF-16やEUC-JPなど、多様な文字コードが混在する環境の場合。

まとめ:エンジニアとしての視点

プログラミングにおいて、エラーは「敵」ではありません。それは「システムの仕組みをより深く理解するための招待状」です。

今回学んだことは、単なる「文字のスキップ術」ではありません。
1. データの物理的な実体(バイト列)と、論理的な解釈(文字コード)の乖離を理解すること。
2. 利用するオブジェクト(FSO vs ADODB)の特性と限界を知ること。
3. 状況に応じて最適な道具を選択すること。

これこそが、初級者を脱し、中級者、そして伝説的なアーキテクトへと登り詰めるための思考プロセスなのです。

さあ、このコードをあなたのプロジェクトに組み込んでみてください。きっと、あの「見えない幽霊」は、もう二度とあなたの邪魔をすることはないでしょう。

応援していますよ。頑張ってくださいね!

タイトルとURLをコピーしました