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

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

【実務中級者向け】VBScriptにおけるBOMの呪縛を解く:堅牢なCSVパース設計の極意

業務自動化の現場において、エンジニアの腕が最も露呈するのは「正常系」の処理ではなく、「文字コードという名の不確定要素」に直面した時だ。

多くの初級者が `FileSystemObject` (FSO) を使ってファイル操作を完結させようとする。しかし、現代のデータ連携において、UTF-8(BOM付き)やUTF-16のCSVファイルは避けて通れない。FSOの `TextStream` でこれらのファイルをそのまま読み込んだ際、先頭に現れる「謎の文字(`` など)」に頭を抱えた経験はないだろうか?

これは単なる「文字化け」ではない。ヘッダー行の列名が一致せず、データベースへのインサートに失敗し、自動化フロー全体が停止する――。プロダクション環境における致命的な設計ミスだ。

本稿では、VBScriptを用いたファイル走査において、BOMを正確に制御し、いかにして「壊れない」パース処理を構築すべきか、そのアーキテクチャを伝授する。

1. なぜ FSO だけでは「不完全」なのか

まず、技術的な原因を明確にしよう。

`Scripting.FileSystemObject` は、極めて強力で扱いやすいオブジェクトだが、その設計思想は古い。FSOの `OpenTextFile` メソッドは、主にシステムのデフォルトエンコーディング(多くの場合 ANSI/Shift-JIS)を前提としている。

UTF-8のBOM(`0xEF, 0xBB, 0xBF`)は、テキストエディタに対して「これはUTF-8である」と伝えるための目印だ。しかし、FSOでこれを読み込むと、FSOはBOMを「データの一部(文字)」として解釈してしまう。その結果、CSVの最初のカラム名が `BOM + ID` のようになり、プログラム側で行う `If colName = “ID”` といった判定がすべて失敗するのだ。

「BOMを後から `Replace` で消去する」という手法は、プロの設計ではない。
それは、文字コードの概念を無視した「対症療法」に過ぎない。真の解決策は、「エンコーディングを明示的に指定してストリームを開く」ことにある。

2. アーキテクチャの転換:FSO と ADODB.Stream の使い分け

堅牢なツールを設計する場合、役割を明確に分離(Separation of Concerns)すべきだ。

1. FileSystemObject (FSO):

  • フォルダの存在確認、ファイルの列挙(走査)、ファイル属性の取得に特化させる。

2. ADODB.Stream:

  • ファイルの内容を「正しいエンコーディング」で読み込むための、低レイヤーなバイナリ/テキストストリームとして利用する。

この二段構えこそが、エンコーディングの多様性に耐えうる設計の基本である。

3. プロダクション・クオリティのコード実装

以下に、複数のCSVファイルを走査し、BOMを適切に処理しながら中身をパースする、保守性の高いモジュール例を示す。

'**
' Module: CSVProcessor
' Description: BOM付きUTF-8/UTF-16ファイルを安全に読み込むための実装例
'**

Option Explicit

' メイン処理のシミュレーション
Call Main()

Sub Main()
    Dim fso, targetFolder, file
    Dim targetPath
    
    targetPath = "C:\AutomatedData\Input" ' 対象ディレクトリ
    
    Set fso = CreateObject("Scripting.FileSystemObject")
    
    If Not fso.FolderExists(targetPath) Then
        WScript.Echo "Error: Target folder not found."
        Exit Sub
    End If
    
    ' 1. FSOによるファイル走査 (File Enumeration)
    For Each file In fso.GetFolder(targetPath).Files
        ' CSVファイルのみを対象とする
        If LCase(fso.GetExtensionName(file.Name)) = "csv" Then
            WScript.Echo "Processing: " & file.Name
            Call ProcessCSVFile(file.Path)
        End If
    Next
    
    Set fso = Nothing
    WScript.Echo "All processes completed."
End Sub

'**
' Function: ProcessCSVFile
' Argument: strFilePath (String) - ファイルのフルパス
' Description: ADODB.Streamを用いてBOMを回避しながら内容を読み込む
'**
Sub ProcessCSVFile(strFilePath)
    Dim adoStream, strContent, arrLines, i
    Dim errMessage
    
    On Error Resume Next ' エラーハンドリングの開始
    
    ' 2. ADODB.Streamによるエンコーディング制御
    ' FSOではなく、ADODB.Streamを使用することでBOMを自動的にスキップさせる
    Set adoStream = CreateObject("ADODB.Stream")
    
    adoStream.Type = 2 ' adTypeText (テキストモード)
    adoStream.Charset = "UTF-8" ' ここでUTF-8を指定。BOMがあれば自動認識して除去される
    adoStream.Open
    adoStream.LoadFromFile strFilePath
    
    If Err.Number <> 0 Then
        errMessage = "Failed to open file: " & Err.Description
        Err.Clear
    Else
        ' 全内容を一括取得(メモリに余裕がある場合)
        strContent = adoStream.ReadText
        adoStream.Close
        
        ' 改行コードで分割して配列化
        arrLines = Split(strContent, vbCrLf)
        
        ' 3. パース処理 (ここに対象業務のロジックを実装)
        Call ParseData(arrLines)
    End If
    
    ' 4. オブジェクトのライフサイクル管理 (重要)
    ' ループ内で大量のファイルを扱う場合、明示的な解放を怠るとメモリリークの原因となる
    Set adoStream = Nothing
    
    If errMessage <> "" Then
        WScript.Echo "Error in " & strFilePath & ": " & errMessage
    End If
    On Error GoTo 0 ' エラーハンドリングのリセット
End Sub

'**
' Sub: ParseData
' Argument: arrLines (Array) - 行ごとの配列
' Description: 実際の業務ロジック(DB登録など)を記述する
'**
Sub ParseData(arrLines)
    Dim i, rowData, colData
    
    ' 1行目はヘッダーとしてスキップ、または検証を行う設計にする
    For i = 1 To UBound(arrLines)
        If Trim(arrLines(i)) <> "" Then ' 空行を除外
            ' カンマ区切りで分割
            rowData = Split(arrLines(i), ",")
            
            ' ここで各カラムの処理を行う
            ' 例: DebugPrint rowData(0) 
        End If
    Next
End Sub

4. リーダーが教える「堅牢な設計」の3つの鉄則

このコードを単なるコピペで終わらせてはいけない。実務で運用する際には、以下の設計思想を脳内に刻んでおくことだ。

① オブジェクトのライフサイクルを管理せよ

VBScriptにおける最大の落とし穴は、ループ内でのオブジェクトの生存期間だ。`ADODB.Stream` や `FileSystemObject` をループ内で生成し、`Set obj = Nothing` による解放を忘れると、ファイルロックが発生したり、メモリ消費が肥大化してサーバープロセスがクラッシュしたりする。「使い終わったら、即座に捨てる」。これが鉄則だ。

② エラーハンドリングを「握りつぶすな」

`On Error Resume Next` は諸刃の剣だ。本稿の例では、エラーが発生した際にメッセージを保持し、処理を継続させる設計にしている。しかし、エラーが発生しているのに「正常に動いている」と誤認させるような設計は、データ整合性を破壊する。エラーが発生した場合は、ログに詳細を記録し、必要であればプロセスを異常終了させる勇気を持て。

③ 文字コードの「仮定」を捨てよ

本稿では `Charset = “UTF-8″` と指定しているが、実務では「UTF-8の場合もあれば、Shift-JISの場合もある」という混沌とした状況が頻発する。
究極の堅牢性を求めるなら、「最初にファイルをバイナリモード(`adBinary`)で読み込み、先頭数バイトをチェックしてエンコーディングを判定する」というロジックを組み込むべきだ。

結びに

BOMの処理は、一見すると些細な問題に見える。しかし、自動化エンジニアにとって、こうした「エッジケース」への対処能力こそが、プロフェッショナルとアマチュアを分かつ境界線だ。

FSOの限界を知り、ADODB.Streamを正しく使いこなし、オブジェクトのライフサイクルを制御する。この一連の設計プロセスをマスターすれば、君が作成するツールは、いかなる不規則なデータに対しても屈しない、真に価値のある資産となるだろう。

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