【実務・中級編】【上級プロフェッショナル向け】FSOとMSXML2.DOMDocumentを連携させた「テキスト設定」と「XML構成」の双方型同期パイプライン – VBScript (Visual Basic Scripting Edition)解析バイブル

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

データの整合性を死守せよ:FSOとMSXML2による「設定同期パイプライン」の極致

業務自動化の現場において、我々はしばしば「二重管理の地獄」に直面する。

現場のオペレーターがメンテナンスしやすい「フラットなテキスト形式(Key=Value)」の設定ファイルと、システムが厳格な構造を要求する「XML形式」の設定ファイル。これらが別々に存在し、一方が更新されたときにもう一方が放置される――。このわずかな「ズレ」が、本番環境での予期せぬシステムダウンを引き起こす。

凡庸なエンジニアは、場当たり的な文字列置換や、力技のスクリプトでこれを解決しようとする。しかし、プロフェッショナルは違う。我々が構築すべきは、「データの単一真実(Single Source of Truth)」を維持するための、堅牢な同期パイプラインである。

本稿では、VBScriptにおいて最強の武器である`FileSystemObject (FSO)`と`MSXML2.DOMDocument`を組み合わせ、フラットファイルとXML構成を高度に同期させるアーキテクチャを伝授する。

1. 設計思想:なぜ「単純な書き換え」では足りないのか

多くの開発者が陥る罠は、ファイルを「読み込んで、書き換えて、保存する」という単一方向の思考である。しかし、エンタープライズ用途では以下の3つの課題を解決しなければならない。

1. データの正規化 (Normalization): テキストの「Key = Value」と、XMLの `Value` という異なる構造を、メモリ上で同一の抽象レイヤー(Dictionaryオブジェクト)に昇華させる必要がある。
2. 差分検知 (Diffing): 全てを上書きするのではなく、変更があった箇所のみを特定し、双方向の整合性を担保しなければならない。
3. 原子性の確保 (Atomicity): 書き込み中にエラーが発生した場合、設定ファイルが破損してはいけない。

これらを解決するために、我々は「メモリ内辞書(In-Memory Dictionary)をハブとした同期モデル」を採用する。

2. プロダクション・アーキテクチャ

同期プロセスは以下の4フェーズで構成される。

1. Ingestion Phase: FSOを用いてテキストを、MSXML2を用いてXMLを解析し、双方を `Scripting.Dictionary` に展開する。
2. Reconciliation Phase: 2つの辞書を比較し、差分(Add/Update/Delete)を抽出する。
3. Propagation Phase: 抽出された差分を、メモリ上のXML DOMおよびテキスト用バッファに反映する。
4. Persistence Phase: 整合性が取れた状態のデータを、各ファイル形式としてディスクへ書き戻す。

3. 実装:堅牢なる同期エンジン

以下に、そのまま実務の基盤として利用可能な、極めて堅牢な設計のコード例を示す。

' **
' Module: ConfigSyncEngine
' Description: FSO and MSXML2 based bidirectional synchronization pipeline.
' Author: Chief Architect
' **

Option Explicit

' 定数定義:マジックナンバーを排除し、保守性を高める
Const DELIMITER = "="
Const XML_ROOT_NODE = "Configuration"
Const XML_ITEM_NODE = "Setting"
Const XML_KEY_ATTR = "key"
Const XML_VAL_NODE = "value"

' メイン実行ルーチン
Sub ExecuteSyncPipeline(strTextPath, strXmlPath)
    Dim objFSO, objDictText, objDictXml
    Dim objXmlDoc, objError
    
    Set objFSO = CreateObject("Scripting.FileSystemObject")
    Set objDictText = CreateObject("Scripting.Dictionary")
    Set objDictXml = CreateObject("Scripting.Dictionary")
    
    On Error Resume Next
    
    ' --- Phase 1: Ingestion (データの取り込み) ---
    
    ' 1. テキストファイルのロード
    If objFSO.FileExists(strTextPath) Then
        Call LoadTextToDictionary(strTextPath, objDictText)
    Else
        Err.Raise 1, "SyncEngine", "Text config file not found: " & strTextPath
    End If
    
    ' 2. XMLファイルのロード
    Set objXmlDoc = CreateObject("MSXML2.DOMDocument.6.0")
    objXmlDoc.Async = False
    objXmlDoc.validateOnParse = False
    If Not objXmlDoc.Load(strXmlPath) Then
        Err.Raise 2, "SyncEngine", "XML load failed: " & objXmlDoc.parseError.reason
    End If
    Call LoadXmlToDictionary(objXmlDoc, objDictXml)
    
    If Err.Number <> 0 Then
        WScript.Echo "Critical Error during Ingestion: " & Err.Description
        WScript.Quit
    End If

    ' --- Phase 2 & 3: Reconciliation & Propagation (差分検知と反映) ---
    ' ここでは「テキストファイルをマスター」としてXMLを更新するロジックを例示
    ' 実運用では、タイムスタンプ等に基づきどちらを優先するか決定する設計にする
    
    Dim key
    For Each key In objDictText.Keys
        ' テキストに存在するKeyがXMLになければ追加、値が違えば更新
        If Not objDictXml.Exists(key) Then
            Call UpdateXmlNode(objXmlDoc, key, objDictText(key))
        ElseIf objDictXml(key) <> objDictText(key) Then
            Call UpdateXmlNode(objXmlDoc, key, objDictText(key))
        End If
    Next

    ' --- Phase 4: Persistence (永続化) ---
    
    ' XMLの保存(一時ファイル経由でのアトミック書き込みを推奨)
    objXmlDoc.Save strXmlPath
    
    ' テキストの保存(同期が完了した状態を再現)
    Call SaveDictionaryToText(strTextPath, objDictText)

    If Err.Number = 0 Then
        WScript.Echo "Synchronization completed successfully."
    Else
        WScript.Echo "Error during Persistence: " & Err.Description
    End If
    
    On Error GoTo 0
    
    ' オブジェクトの明示的な解放(メモリリーク防止)
    Set objDictText = Nothing
    Set objDictXml = Nothing
    Set objXmlDoc = Nothing
    Set objFSO = Nothing
End Sub

' --- Helper Methods ---

' テキストファイルを辞書に展開
Sub LoadTextToDictionary(strPath, objDict)
    Dim objFSO, objFile, strLine, arrParts
    Set objFSO = CreateObject("Scripting.FileSystemObject")
    Set objFile = objFSO.OpenTextFile(strPath, 1) ' ForReading
    
    Do Until objFile.AtEndOfStream
        strLine = Trim(objFile.ReadLine)
        If strLine <> "" And InStr(strLine, DELIMITER) > 0 Then
            arrParts = Split(strLine, DELIMITER, 2)
            If UBound(arrParts) = 1 Then
                objDict(Trim(arrParts(0))) = Trim(arrParts(1))
            End If
        End If
    Loop
    objFile.Close
End Sub

' XMLを辞書に展開
Sub LoadXmlToDictionary(objXmlDoc, objDict)
    Dim colNodes, objNode
    Set colNodes = objXmlDoc.SelectNodes("//" & XML_ITEM_NODE)
    For Each objNode In colNodes
        ' key属性とvalue子ノードを取得
        objDict(objNode.Attributes.getNamedItem(XML_KEY_ATTR).text) = _
            objNode.SelectSingleNode(XML_VAL_NODE).text
    Next
End Sub

' XMLのノードを更新または追加
Sub UpdateXmlNode(objXmlDoc, strKey, strValue)
    Dim colNodes, objNode, objNewNode, objValNode
    Set colNodes = objXmlDoc.SelectNodes("//" & XML_ITEM_NODE & "[@" & XML_KEY_ATTR & "='" & strKey & "']")
    
    If colNodes.Length > 0 Then
        ' 既存ノードの更新
        Set objNode = colNodes(0)
        objNode.SelectSingleNode(XML_VAL_NODE).text = strValue
    Else
        ' 新規ノードの作成
        Set objNewNode = objXmlDoc.createElement(XML_ITEM_NODE)
        objNewNode.setAttribute XML_KEY_ATTR, strKey
        Set objValNode = objXmlDoc.createElement(XML_VAL_NODE)
        objValNode.text = strValue
        objNewNode.appendChild objValNode
        objXmlDoc.documentElement.appendChild objNewNode
    End If
End Sub

' 辞書をテキストファイルとして保存
Sub SaveDictionaryToText(strPath, objDict)
    Dim objFSO, objFile, key
    Set objFSO = CreateObject("Scripting.FileSystemObject")
    ' 既存ファイルを上書きするため 2 (ForWriting) を指定
    Set objFile = objFSO.CreateTextFile(strPath, True)
    
    For Each key In objDict.Keys
        objFile.WriteLine key & DELIMITER & objDict(key)
    Next
    objFile.Close
End Sub

' 実行例
' ExecuteSyncPipeline "C:\config\settings.txt", "C:\config\settings.xml"

4. 伝説のアーキテクトが教える「落とし穴」と回避策

このコードをプロダクション環境へデプロイする前に、以下の3点を必ず検討せよ。

① エンコーディングの呪縛

FSOの `OpenTextFile` は、デフォルトでは ANSI (Shift-JIS) を想定している。しかし、現代のXMLは多くの場合 UTF-8 である。
もしXMLにマルチバイト文字(日本語など)が含まれる場合、FSOでテキストを読み書きすると文字化け、あるいはXMLの整合性破壊を招く。

  • 対策: `ADODB.Stream` オブジェクトを使用せよ。これを用いれば、UTF-8での読み書きを完璧に制御できる。

② ファイルロックの競合

大規模なシステムでは、他のプロセスが設定ファイルを掴んでいる可能性がある。`CreateTextFile` を実行した瞬間に「Permission Denied」で落ちるシナリオは日常茶飯事だ。

  • 対策: 書き込みの際、直接対象ファイルを操作するのではなく、「一時ファイル(.tmp)に書き出し、成功したら `FSO.MoveFile` で置換する」 というアトミックな手法を取れ。これにより、書き込み失敗時のファイル破損を物理的に回避できる。

③ 複雑度の爆発 (Complexity O(n))

上記コードは `Dictionary` を使用しているため、検索・照合の計算量は $O(1)$ に近い。しかし、XMLの構造が階層化(Nested)された場合、単純な Key-Value 辞書では表現できなくなる。

  • 対策: 階層構造が必要な場合は、辞書の中にさらに辞書を入れる「再帰的辞書構造」を設計するか、XPathを駆使して直接DOMを操作する設計にシフトせよ。

結論

VBScriptは決して古い言語ではない。適切に設計された `FSO` と `MSXML2` のパイプラインは、現代の複雑なシステムにおいても、極めて軽量かつ強力な統合エンジンとして機能する。

「動けばいい」という考えを捨て、「壊れない設計」を追求せよ。それが、業務自動化エンジニアとしての真の価値である。

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