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

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

文字コードの混沌を統治せよ:FSOとADODB.Streamによる堅牢なログ統合パイプラインの設計

業務自動化の現場において、我々が直面する最も卑俗で、かつ最も致命的な問題は何だ?
それは、アルゴリズムの複雑さでも、計算リソースの不足でもない。「文字コードの混在」だ。

レガシーなシステムが吐き出す `Shift_JIS`、モダンなWebサービスが生成する `UTF-8`、そしてWindows内部処理が刻む `UTF-16`。これらが混在するログ群を、安易に `FileSystemObject (FSO)` の `OpenTextFile` だけで捌こうとする者がいる。もし君がそう考えているなら、今すぐその設計思想を捨て去るべきだ。FSOのテキストストリームは、文字コードの自動判別能力において極めて脆弱であり、境界条件において容易に「文字化け」という名の崩壊を招く。

本稿では、プロフェッショナルが実装すべき、「FSOによる構造探索」と「ADODB.Streamによる文字コード変換」を高度に融合させた、多層統合パイプラインの設計指針を伝授する。

1. なぜ FSO だけでは不十分なのか:アーキテクトの視点

FSOは、ファイルシステムという「構造」を操作するには世界最高峰のツールだ。ディレクトリの走査、ファイルの存在確認、属性の操作。これらにおいてFSOの右に出るものはない。

しかし、FSOは「データの意味(エンコーディング)」を理解していない。
`OpenTextFile` メソッドで指定できるのは、実質的に `Tristate`(ASCII, ANSI, Unicode)の3パターンのみだ。現代の多様な `UTF-8`(BOMあり/なし)や、特定のエンコーディングを正確にハンドリングするには、FSOの能力を「ファイル探索」に限定し、データの「読み書き」は別の専門家に任せるという責務の分離(Separation of Concerns)が必要不可欠となる。

そこで登場するのが `ADODB.Stream` だ。これはバイナリレベルでの制御を可能にし、任意の `Charset` を指定してストリームを構築できる、文字コード変換の「錬金術師」である。

2. 設計戦略:3フェーズ・パイプライン

堅牢なパイプラインは、以下の3つのフェーズで構成される。

1. Discovery Phase (FSO): 指定ディレクトリを再帰的に走査し、処理対象のファイルリストを抽出する。
2. Identification Phase (Binary Analysis): ファイルの先頭数バイトをバイナリとして読み込み、BOM (Byte Order Mark) を解析して真の文字コードを判定する。
3. Transformation Phase (ADODB.Stream): 判定された文字コードをソースとして開き、標準化されたターゲット文字コード(例:UTF-8)へ変換しながら書き出す。

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

以下に、実務でそのまま投入可能なレベルの VBScript 実装を示す。単なるコピペではなく、各モジュールの意図を理解してほしい。

' **
' Module: EncodingUnifiedPipeline
' Description: Mixed encoding log files to standardized UTF-8 pipeline.
' Author: Chief Architect
' **

Option Explicit

' --- 設定定数 ---
Const TARGET_ENCODING = "UTF-8" ' 最終的な統一フォーマット
Const LOG_DIR         = "C:\Logs\Source" ' 調査対象ディレクトリ
Const OUTPUT_FILE     = "C:\Logs\Unified_Log.txt" ' 統合出力先

Dim fso, streamIn, streamOut
Set fso = CreateObject("Scripting.FileSystemObject")

' メインプロセス実行
Main

Sub Main()
    On Error Resume Next
    
    If Not fso.FolderExists(LOG_DIR) Then
        WScript.Echo "Error: Source directory not found."
        Exit Sub
    End If

    ' 統合用出力ストリームの初期化
    Set streamOut = CreateObject("ADODB.Stream")
    streamOut.Type = 2 ' adTypeText
    streamOut.Charset = TARGET_ENCODING
    streamOut.Open

    WScript.Echo "Pipeline Started..."

    ' 1. Discovery Phase: ファイル走査
    ProcessFolder fso.GetFolder(LOG_DIR)

    ' 2. Finalization
    streamOut.SaveToFile OUTPUT_FILE, 2 ' adSaveCreateOverWrite
    streamOut.Close
    
    WScript.Echo "Pipeline Completed Successfully. Output: " & OUTPUT_FILE
    
    If Err.Number <> 0 Then
        WScript.Echo "Critical Error: " & Err.Description
    End If
End Sub

' 再帰的フォルダ走査
Sub ProcessFolder(folder)
    Dim subFolder, file
    
    ' ファイルの処理
    For Each file In folder.Files
        ' ログファイル(.log)のみを対象とする
        If LCase(fso.GetExtensionName(file.Name)) = "log" Then
            WScript.Echo "Processing: " & file.Name
            TransformFile file.Path
        End If
    Next

    ' サブフォルダの再帰処理
    For Each subFolder In folder.SubFolders
        ProcessFolder subFolder
    Next
End Sub

' Transformation Phase: 文字コード判定と変換
Sub TransformFile(filePath)
    Dim encoding, content
    
    ' 判定フェーズ
    encoding = DetectEncoding(filePath)
    
    ' 読み込みフェーズ (ADODB.Streamを使用)
    Set streamIn = CreateObject("ADODB.Stream")
    streamIn.Type = 2 ' adTypeText
    streamIn.Charset = encoding
    streamIn.Open
    
    ' バイナリ読み込みによるエラー回避のため、まずパスを開く
    streamIn.LoadFromFile filePath
    
    ' 全内容を読み込み(メモリ制約がある場合はチャンク処理を検討せよ)
    content = streamIn.ReadText
    streamIn.Close

    ' 統合出力ストリームへ書き込み
    If content <> "" Then
        streamOut.WriteText content & vbCrLf
    End If
End Sub

' Identification Phase: BOMに基づく文字コード判定
' 判定ロジックの精度がパイプラインの生命線である
Function DetectEncoding(filePath)
    Dim binStream, bom, hexBOM
    
    ' バイナリモードで先頭数バイトを読み取る
    Set binStream = CreateObject("ADODB.Stream")
    binStream.Type = 1 ' adTypeBinary
    binStream.Open
    binStream.LoadFromFile filePath
    
    ' 先頭4バイトを確保(BOM判定用)
    If binStream.Size < 4 Then
        bom = binStream.Read
    Else
        bom = binStream.Read(4)
    End If
    binStream.Close

    ' バイナリを解析して判定
    ' UTF-8: EF BB BF
    ' UTF-16 LE: FF FE
    ' UTF-16 BE: FE FF
    
    ' 簡易的なバイト判定(実務的なヒューリスティック)
    If IsMatch(bom, Array(&HEF, &HBB, &HBF)) Then
        DetectEncoding = "UTF-8"
    ElseIf IsMatch(bom, Array(&HFF, &HFE)) Then
        DetectEncoding = "UTF-16"
    ElseIf IsMatch(bom, Array(&HFE, &HFF)) Then
        DetectEncoding = "UTF-16BE"
    Else
        ' BOMがない場合は、伝統的な Shift_JIS (ANSI) と見なす
        ' ※ 厳密には文字コード検知エンジンが必要だが、実務上はこれで8割をカバーできる
        DetectEncoding = "Shift_JIS"
    End If
End Function

' 配列パターンマッチング用ヘルパー
Function IsMatch(binData, patternArray)
    Dim i, match
    match = True
    For i = 0 To UBound(patternArray)
        If binData(i) <> patternArray(i) Then
            match = False
            Exit For
        End If
    Next
    IsMatch = match
End Function

4. プロフェッショナルのための知見:運用上の注意点

このコードをプロダクション環境に投入する際、以下の3点に留意せよ。

① メモリ管理と巨大ファイルへの対応

上記のコードは `streamIn.ReadText` を用いてファイル全体をメモリにロードしている。数GB規模のログファイルを扱う場合、このアプローチは `Out of Memory` を引き起こす。その場合は、`streamIn.ReadText(-1)` ではなく、固定長でのチャンク読み込み、あるいは `streamIn.Skip` を用いたストリーム制御に設計を変更する必要がある。

② 「BOMなしUTF-8」という伏兵

実世界のログの多くは「BOMなしUTF-8」である。この場合、私のコードでは `Shift_JIS` として判定される。もし対象システムが確実にUTF-8を吐き出していることが分かっているなら、判定ロジックをバイナリ解析ではなく、ファイル拡張子やシステムプロパティに基づく「事前定義ルール」に切り替えるのが、最も賢明なエンジニアリングだ。

③ 権限と排他制御

`FSO` による走査中、他のプロセスがログを書き込み中(ロック状態)である可能性がある。`On Error Resume Next` は、エラーを無視するためではなく、「エラーが発生した際に、どのファイルで、どのようなエラー(Permission Denied等)が起きたかを捕捉するため」に使うべきだ。エラーハンドリングを疎かにする者は、パイプラインの信頼性を担保できない。

結論

FSOは「地図」であり、ADODB.Streamは「翻訳機」である。
これらを正しく組み合わせ、それぞれのオブジェクトのライフサイクルと特性を理解して初めて、文字コードの混沌を統治する堅牢な自動化ツールが完成する。

単に「動く」コードを書くのではない。「壊れない」設計をせよ。それがプロフェッショナルの責務である。

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