【テクニカル・上級編】【上級プロフェッショナル向け】FSOとADODB.Streamを多層結合し Shift_JIS / UTF-8 / UTF-16 が混在する複数ログの一括文字コード統一パイプライン – VBScript (Visual Basic Scripting Edition)解析バイブル

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

文字コードの混沌を統べる:FSOとADODB.Streamによる多層結合型文字コード統一パイプライン

レガシーな基幹システムが鎮座するエンタープライズの現場において、データは決して「綺麗」ではない。
あるシステムは Shift_JIS でログを吐き出し、あるAPIは UTF-8 (BOMなし) を返し、またある古いWindowsアプリケーションは UTF-16 LE でデータを固める。これらが混在するディレクトリを前にして、安易な `FileSystemObject` (FSO) の `TextStream` だけに頼る者は、必ず文字化けという名の泥沼に沈むことになる。

本稿では、ファイルシステムの走査を担う FSO と、バイナリ・ストリームの制御を司る ADODB.Stream を、単なるライブラリとしてではなく、一つの「パイプライン」として多層結合させる、極限のデータ統合手法を解説する。

1. arquitectural strategy: なぜ FSO だけでは足りないのか

多くの初学者は、FSO の `OpenTextFile` を使ってファイルを読み込もうとする。しかし、FSO の `TextStream` は、システムのデフォルト・コードページ(日本環境であれば Shift_JIS)に強く依存しており、UTF-8 や UTF-16 の柔軟なハンドリングには致命的な欠陥がある。

我々プロフェッショナルが取るべき戦略は、「構造の探索は FSO、データの解釈と変換は ADODB.Stream」 という役割分離(Separation of Concerns)である。

  • FSO (FileSystemObject): ディレクトリツリーの再帰的走査、ファイル属性の取得、物理的なファイル操作に特化させる。
  • ADODB.Stream: バイナリレベルでのエンコーディング判定、およびメモリ上での文字コード変換(Transcoding)に特化させる。

この二つを結合し、メモリリークを最小限に抑えながら、文字コードの混在を「決定論的」に処理するパイプラインを構築する。

2. 核心部:BOM(Byte Order Mark)による決定論的判定

文字コードを「推測」してはいけない。推測はエラーの温床である。
プロフェッショナルは、ファイルの先頭数バイトをバイナリとして読み込み、BOM (Byte Order Mark) を検証することで、そのファイルの正体を確定させる。

  • `FF FE` $\rightarrow$ UTF-16 LE
  • `FE FF` $\rightarrow$ UTF-16 BE
  • `EF BB BF` $\rightarrow$ UTF-8
  • それ以外 $\rightarrow$ システム規定 (Shift_JIS)

この判定ロジックをパイプラインの「ゲートウェイ」として実装する。

3. 実装:高堅牢・低メモリ負荷の変換エンジン

以下に、実戦投入可能な VBScript による実装を示す。このコードは、単なる変換スクリプトではない。オブジェクトのライフサイクルを厳格に管理し、大量のログファイルを処理してもリソースを食いつぶさないように設計されている。

' ==============================================================================
' Encoding Unification Pipeline
' Author: Legendary Chief Architect
' Description: FSO and ADODB.Stream based multi-encoding conversion engine.
' ==============================================================================
Option Explicit

' 定数定義:エンコーディングの定義
Const ENCODING_UTF8 = "UTF-8"
Const ENCODING_UTF16 = "Unicode" ' ADODB.Stream における UTF-16 LE
Const ENCODING_SJIS = "Shift_JIS"

' 設定
Dim strSourceDir, strTargetDir
strSourceDir = "C:\Logs\Source"      ' 変換元ディレクトリ
strTargetDir = "C:\Logs\Unified"     ' 変換後出力先

' メインプロセス実行
Call ExecutePipeline(strSourceDir, strTargetDir)

Sub ExecutePipeline(srcDir, destDir)
    Dim objFSO, objFolder, objFile
    Set objFSO = CreateObject("Scripting.FileSystemObject")
    
    ' 出力先ディレクトリの存在確認と作成
    If Not objFSO.FolderExists(destDir) Then
        objFSO.CreateFolder(destDir)
    End If

    ' ディレクトリ走査
    If Not objFSO.FolderExists(srcDir) Then
        WScript.Echo "Error: Source directory not found."
        Exit Sub
    End If

    Set objFolder = objFSO.GetFolder(srcDir)
    
    For Each objFile In objFolder.Files
        ' ログファイルのみを対象とする(拡張子によるフィルタリング)
        If LCase(objFSO.GetExtensionName(objFile.Name)) = "log" Then
            WScript.Echo "Processing: " & objFile.Name
            Call ConvertFileWithDetection(objFile.Path, objFSO.BuildPath(destDir, objFile.Name))
        End If
    Next

    ' オブジェクトの明示的な解放(メモリリーク防止の鉄則)
    Set objFolder = Nothing
    Set objFSO = Nothing
    WScript.Echo "Pipeline execution completed successfully."
End Sub

' ファイルの文字コードを判定し、UTF-8に統一して書き出す
Sub ConvertFileWithDetection(strFilePath, strDestPath)
    Dim objStreamIn, objStreamOut, objFSO
    Dim strCharset, binaryData
    
    Set objFSO = CreateObject("Scripting.FileSystemObject")
    
    ' 1. バイナリモードで先頭を確認し、文字コードを判定する
    strCharset = DetectCharset(strFilePath)
    
    ' 2. ADODB.Stream を用いた変換パイプラインの構築
    On Error Resume Next ' 実行時エラーによるプロセス停止を回避
    
    Set objStreamIn = CreateObject("ADODB.Stream")
    Set objStreamOut = CreateObject("ADODB.Stream")
    
    ' 入力ストリームの設定
    objStreamIn.Type = 2 ' adTypeText
    objStreamIn.Charset = strCharset
    objStreamIn.Open
    objStreamIn.LoadFromFile strFilePath
    
    ' 出力ストリームの設定 (常に UTF-8 で統一)
    objStreamOut.Type = 2 ' adTypeText
    objStreamOut.Charset = ENCODING_UTF8
    objStreamOut.Open
    
    ' データの転送 (In $\rightarrow$ Out)
    objStreamIn.CopyTo objStreamOut
    
    ' 書き出し
    objStreamOut.SaveToFile strDestPath, 2 ' adSaveCreateOverWrite
    
    If Err.Number <> 0 Then
        WScript.Echo "  [!] Error processing " & strFilePath & ": " & Err.Description
        Err.Clear
    Else
        WScript.Echo "  [+] Converted (" & strCharset & " $\rightarrow$ UTF-8)"
    End If
    On Error GoTo 0

    ' 3. ライフサイクルの終焉:オブジェクトの徹底的な解放
    ' これを行わないと、大量のファイル処理時にメモリが枯渇し、ファイルロックが発生する
    objStreamIn.Close
    Set objStreamIn = Nothing
    objStreamOut.Close
    Set objStreamOut = Nothing
    Set objFSO = Nothing
End Sub

' BOMを解析して文字コードを決定する低レイヤー関数
Function DetectCharset(strFilePath)
    Dim objStream, binData, strBOM
    ' バイナリ読み込み用のストリーム
    Set objStream = CreateObject("ADODB.Stream")
    objStream.Type = 1 ' adTypeBinary
    objStream.Open
    objStream.LoadFromFile strFilePath
    
    ' 先頭4バイトを読み込む(BOM判定用)
    binData = objStream.Read(4)
    objStream.Close
    Set objStream = Nothing
    
    ' バイナリを16進数文字列に変換して比較
    strBOM = ConvertBinaryToHex(binData)
    
    ' BOM判定ロジック
    If Left(strBOM, 4) = "EFBBBF" Then
        DetectCharset = ENCODING_UTF8
    ElseIf Left(strBOM, 4) = "FFFE" Then
        DetectCharset = ENCODING_UTF16
    ElseIf Left(strBOM, 4) = "FEFF" Then
        DetectCharset = ENCODING_UTF16 ' UTF-16 BE は稀だが考慮
    Else
        ' BOMが見当たらない場合は、レガシーな Shift_JIS と断定する
        DetectCharset = ENCODING_SJIS
    End If
End Function

' バイナリデータを16進数文字列に変換するユーティリティ
Function ConvertBinaryToHex(binData)
    Dim i, strHex
    strHex = ""
    For i = 1 To LenB(binData)
        strHex = strHex & Right("0" & Hex(AscB(MidB(binData, i, 1))), 2)
    Next
    ConvertBinaryToHex = strHex
End Function

4. アーキテクトの視点:パフォーマンスと堅牢性の極意

このコードを実戦で使用するにあたり、以下の「設計思想」を理解しておく必要がある。

オブジェクトのライフサイクル管理

VBScript における `Set obj = Nothing` は、単なる作法ではない。特に `ADODB.Stream` のような COM オブジェクトは、明示的に `Close` し、参照を解除しない限り、メモリ上に残り続け、ファイルシステムに対してロックをかけ続ける。大量のファイルをループで処理する場合、この管理の甘さが「システム停止」を招く。

エラーハンドリングの境界線

`On Error Resume Next` を乱用するのは素人の仕事だ。本実装では、変換のコア部分にのみ局所的に適用し、エラーが発生した場合にはログを記録して、次のファイルへ「決定論的に」進むように設計している。一つの破損したファイルが、パイプライン全体の停止を引き起こしてはならない。

拡張性への配慮

もし将来、UTF-32 や他のエンコーディングが必要になったとしても、`DetectCharset` 関数のロジックを拡張し、定数を追加するだけで、パイプライン全体の構造を壊さずに対応が可能である。

結論

文字コードの混在は、レガシーシステムとモダンなシステムが共存する現代において、避けては通れない「摩擦」である。その摩擦を、FSOによる構造把握と、ADODB.Streamによる精密なデータ操作で制御する。これが、混沌としたデータ環境を統治するための、エンジニアリングの真髄である。

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