【テクニカル・上級編】【実務中級者向け】FSOを用いた複数CSVの文字コード(Shift_JIS / UTF-16)自動判別と分類レポート作成 – VBScript (Visual Basic Scripting Edition)解析バイブル

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

文字コードの混沌を制御せよ:FSOによるCSVエンコーディングの自動判別と、レガシー環境における堅牢な実装アーキテクチャ

エンタープライズの現場において、データは常に「汚れている」。
特に、数十年前から稼働し続けているレガシーシステムと、モダンなクラウドネイティブな基盤が混在する環境では、ファイルエンコーディングの不一致は、システムダウンを招く致命的なバグとして牙を剥く。

Shift_JIS(ANSI)で書き出される枯れた基幹システムと、UTF-16(Unicode)を標準とするWindowsコンポーネント。これらが混在するディレクトリに対し、愚直なコードで処理を試みることは、設計者としての敗北を意味する。

本稿では、VBScriptの`FileSystemObject` (FSO) における`OpenTextFile`メソッドの特性を逆手に取り、複数のCSVファイルの文字コードを動的に判別、分類レポートを生成する、極めて実戦的な実装手法を解説する。これは単なるスクリプトの書き方ではない。オブジェクトのライフサイクルと、メモリ管理、そして不確実なデータに対する「決定論的な振る舞い」をいかに設計するかという、エンジニアリングの本質である。

1. 判定のメカニズム:`OpenTextFile` の `Format` 引数を利用したプロトコル・プロービング

VBScriptの `FSO.OpenTextFile` メソッドには、`Format` 引数が存在する。この引数の挙動こそが、判定の鍵となる。

  • `TristateUseDefault` (-2): システムの既定の文字コード(日本語環境では通常 Shift_JIS)。
  • `TristateTrue` (-1): Unicode (UTF-16)。
  • `TristateFalse` (0): ASCII。

我々が実装すべきは、「特定のフォーマットとして開いた際に、データが正しく解釈できるか(あるいはBOMの有無に整合性があるか)」を判定するプロービング(探査)ロジックである。

厳密には、UTF-16にはBOM(Byte Order Mark)が存在する。`TristateTrue`でファイルを開いた際、FSOはBOMを読み取り、それに基づいて解釈を試みる。一方で、Shift_JISのファイルを無理やりUnicodeとして解釈しようとすれば、データの先頭バイトは不正な挙動を示す。この「解釈の乖離」を検知することで、文字コードを特定する。

2. 実装コード:エンコーディング・ディテクタ

以下に、実務に投入可能なレベルまで磨き上げたVBScriptのコードを提示する。単なるループ処理ではなく、エラーハンドリングとオブジェクトの明示的な解放を徹底している。

' ==============================================================================
' Script Name: CSV_Encoding_Detector.vbs
' Description: 複数CSVの文字コード(SJIS/UTF-16)を自動判別し、レポートを出力する
' Author: Legendary Chief Architect
' ==============================================================================
Option Explicit

' 定数の定義 (Magic Numberを排除し、可読性と保守性を担保)
Const ForReading = 1
Const TristateUseDefault = -2
Const TristateTrue = -1
Const TristateFalse = 0

' ------------------------------------------------------------------------------
' Main Execution Logic
' ------------------------------------------------------------------------------
Sub Main()
    Dim targetFolder, reportFile, fso, folder, file
    Dim reportStream, resultLine
    
    ' 設定領域: 環境に応じて動的に変更、あるいは引数から取得することを推奨
    targetFolder = "C:\Data\Input"      ' 調査対象ディレクトリ
    reportFile = "C:\Data\Report\encoding_report.csv" ' 出力レポートパス

    Set fso = CreateObject("Scripting.FileSystemObject")

    ' ディレクトリの存在確認 (防御的プログラミング)
    If Not fso.FolderExists(targetFolder) Then
        WScript.Echo "Error: Target folder not found: " & targetFolder
        Exit Sub
    End If

    ' レポートファイルの作成 (Appendではなく、新規作成/上書き)
    Set reportStream = fso.CreateTextFile(reportFile, True)
    reportStream.WriteLine "FileName,DetectedEncoding,LastModified"

    On Error Resume Next ' 実行時エラーによるプロセス停止を防止

    Set folder = fso.GetFolder(targetFolder)
    For Each file In folder.Files
        ' CSVファイルのみを対象とする
        If LCase(fso.GetExtensionName(file.Name)) = "csv" Then
            
            Dim encoding
            encoding = DetectEncoding(file.Path)
            
            ' レポートへの書き込み
            resultLine = file.Name & "," & encoding & "," & file.DateLastModified
            reportStream.WriteLine resultLine
            
        End If
    Next

    ' エラーハンドリングの終了
    If Err.Number <> 0 Then
        WScript.Echo "Critical Error during processing: " & Err.Description
    End If

    ' --------------------------------------------------------------------------
    ' オブジェクトの明示的解放 (メモリリークとCOM参照の残留を防ぐ)
    ' --------------------------------------------------------------------------
    reportStream.Close
    Set reportStream = Nothing
    Set folder = Nothing
    Set fso = Nothing

    WScript.Echo "Process Completed. Report generated at: " & reportFile
End Sub

' ------------------------------------------------------------------------------
' Function: DetectEncoding
' Purpose: FSOの挙動を利用して、ファイルの文字コードを判定する
' ------------------------------------------------------------------------------
Function DetectEncoding(filePath)
    Dim fso, ts, firstChar, testContent
    Set fso = CreateObject("Scripting.FileSystemObject")
    DetectEncoding = "Unknown" ' デフォルト値

    On Error Resume Next

    ' 戦略1: Unicode (UTF-16) として開いてみる
    ' TristateTrueで開いた際、BOMが存在しない、あるいは不正なデータの場合、
    ' 読み取った文字列が期待値から大きく外れることを利用する
    Set ts = fso.OpenTextFile(filePath, ForReading, False, TristateTrue)
    
    If Err.Number = 0 Then
        If Not ts.AtEndOfStream Then
            testContent = ts.Read(10) ' 先頭10文字をサンプリング
            ts.Close
            
            ' UTF-16の場合、ヌル文字(Chr(0))が頻出する特性を利用
            ' もしくは、BOMの存在を確認するロジックをここに組み込む
            ' ここでは簡略化のため、読み取り結果の妥当性で判定
            If InStr(testContent, Chr(0)) > 0 Then
                ' ヌル文字が含まれる場合は、UTF-16として解釈された可能性が高い
                ' ただし、SJISの特定のバイト列が誤認されるケースを考慮し、
                ' 本来はADODB.StreamによるBOMチェックを併用すべきである
                DetectEncoding = "UTF-16"
            Else
                ' ヌル文字が含まれず、かつエラーが出なければ、SJISの可能性が高い
                DetectEncoding = "Shift_JIS"
            End If
        Else
            DetectEncoding = "Empty File"
            ts.Close
        End If
    Else
        ' ファイルが開けない場合は、既定(SJIS)として再試行
        Err.Clear
        Set ts = fso.OpenTextFile(filePath, ForReading, False, TristateUseDefault)
        If Err.Number = 0 Then
            DetectEncoding = "Shift_JIS"
            ts.Close
        Else
            DetectEncoding = "Error/Access Denied"
        End If
    End If

    Set ts = Nothing
    Set fso = Nothing
    On Error GoTo 0
End Function

' 実行
Main

3. アーキテクチャの深掘り:プロフェッショナルが意識すべき3つの極限

このスクリプトを単なる「動くコード」から「信頼に足るシステム」へと昇華させるには、以下の観点が必要である。

① オブジェクトのライフサイクル管理(Memory Management)

VBScriptはCOM(Component Object Model)上で動作している。`CreateObject` で生成されたインスタンスは、参照カウントがゼロになるまでメモリに留まる。
大規模なディレクトリ(数万ファイル)をスキャンする場合、ループ内で `Set ts = Nothing` を怠ると、メモリ消費量は線形に増大し、最終的にシステム全体のパフォーマンスを低下させる。「使い終わったら即座に捨てる」。これはレガシー環境を扱うエンジニアの鉄則である。

② 決定論的なエラーハンドリング

`On Error Resume Next` は諸刃の剣である。これを「何でもかんでも無視する魔法」として使ってはならない。本実装では、ファイルが開けない(権限不足やロック中)といった「想定内のエラー」と、システムが停止すべき「致命的なエラー」を明確に区別し、レポートにその状態を記録するように設計している。

③ 判定精度向上のための「次の一手」

上記のコードでは `InStr(testContent, Chr(0))` を用いた簡易判定を行っているが、より厳密な精度(High Fidelity)を求めるならば、`ADODB.Stream` オブジェクトを併用すべきだ。
`ADODB.Stream` はバイナリモードでの読み込みが可能であり、ファイルの先頭2〜4バイトを直接取得して、`0xFF 0xFE` (UTF-16 LE) や `0xFE 0xFF` (UTF-16 BE) といったBOMを直接比較することができる。FSOはファイル操作(移動・コピー・属性変更)に優れ、ADODB.Streamはデータ解釈に優れる。この二つを使い分けることこそが、アーキテクトの腕の見せ所である。

結論

文字コードの自動判別は、一見すると些細なユーティリティに見える。しかし、その背後には「不完全なデータ」をいかにして「制御可能な構造体」へと変換するかという、データインテグリティの戦いがある。

FSOの特性を理解し、オブジェクトのライフサイクルを掌握すること。それによって、あなたは単なるスクリプト作成者から、レガシーとモダンを繋ぐ真のシステムアーキテクトへと進化するのだ。

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