【テクニカル・上級編】【実務中級者向け】テキストファイル内のYAML/JSON風インデント構造をFSOと再帰処理で解析しDictionary化するパース術 – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク

【実務中級者向け】外部ライブラリ一切不要:VBScriptとFSOによる階層インデント・パーサの極意

レガシーなWindows環境において、VBScript(Visual Basic Scripting Edition)は依然としてインフラストラクチャの自動化、キッティング、そしてレガシーシステム間のデータ連携において最前線を死守している。

サードパーティ製のDLLやCOMコンポーネントの持ち込みが厳格に禁じられた閉域網のエンタープライズ環境において、我々エンジニアが頼れるのはOS標準のプリミティブな機能だけだ。特に、設定ファイルとしてJSONやYAMLのような階層構造を持つテキストを読み込み、動的に処理したいという要求は実務上非常に多い。

今回は、`Scripting.FileSystemObject`(FSO)と`Scripting.Dictionary`を駆使し、外部ライブラリに一切依存することなく、インデント構造(スペースやタブの深さ)からネストされたデータ構造を構築する「テキストパーサ」の極限実装を解説する。

—

1. VBScriptにおける「構造化データ」の限界と突破口

VBScriptのネイティブなデータ構造には、Pythonの辞書やJavaScriptのオブジェクト、あるいはJSONのような「入れ子(ネスト)を表現するエレガントな仕組み」が標準ではない。一次元の配列や`Scripting.Dictionary`は存在するが、Dictionaryの値として別のDictionaryを格納することは一見して複雑なポインタ操作(参照渡し)を伴うため、実装を誤るとメモリリークや意図しない参照共有を引き起こす。

この壁を突破するために、以下のアーキテクチャを採用する。

1. FSOによるストリーム読み込み:`OpenTextFile`を用いてメモリ効率よく1行ずつテキストを走査する。
2. インデント深度(Depth)の数値化:行頭のスペース数をカウントし、現在の階層レベルを特定する。
3. スタックベースの再帰的構造化:親要素への参照をスタック(または動的配列)で保持し、現在の階層に適切なDictionaryをアタッチする。

—

2. 実装コード:インデントパーサの完全版

以下のコードは、エラーハンドリング、メモリの明示的解放、そしてインデント構造の解析ロジックを極限まで最適化した実務投入可能なVBScriptのフルスクリプトである。

Option Explicit

‘ ==============================================================================
‘ 処理名: インデント構造設定ファイル パーサ
‘ 概要: YAML/JSON風のインデントテキストを読み込み、ネストされたDictionaryを構築する
‘ ==============================================================================

Dim fso, targetFile, configData
Dim filePath

filePath = “C:\Config\settings.txt” ‘ 解析対象のファイルパス

‘ オブジェクトの生成
Set fso = CreateObject(“Scripting.FileSystemObject”)

If Not fso.FileExists(filePath) Then
WScript.Echo “[ERROR] 指定された設定ファイルが存在しません: ” & filePath
WScript.Quit 1
End If

‘ パース実行
Set configData = ParseIndentedConfig(fso, filePath)

‘ 構築されたデータの検証(ダンプ出力)
If Not configData Is Nothing Then
WScript.Echo “=== パース結果の検証 ===”
Call DumpDictionary(configData, “”)
End If

‘ 明示的なオブジェクト解放(メモリ最適化)
Set configData = Nothing
Set fso = Nothing

WScript.Echo “処理が正常に完了しました。”
WScript.Quit 0

‘ ==============================================================================
‘ 関数名: ParseIndentedConfig
‘ 引数: fso (FileSystemObject), filePath (String)
‘ 戻り値: Scripting.Dictionary (ルートオブジェクト)
‘ ==============================================================================
Function ParseIndentedConfig(fso, filePath)
Dim fileStream, line, rawLine, indentSize
Dim rootDict, stackKeys, stackDicts, currentDepth
Dim colonPos, key, val
Dim i, parentDict, currentDict

Set fileStream = fso.OpenTextFile(filePath, 1, False) ‘ ForReading
Set rootDict = CreateObject(“Scripting.Dictionary”)

‘ 階層管理用のスタック(配列で代用)
‘ stackKeys: 親のキー名を保持
‘ stackDicts: 各階層のDictionaryオブジェクトの参照を保持
Dim maxDepth: maxDepth = 100
ReDim stackKeys(maxDepth)
ReDim stackDicts(maxDepth)

Set stackDicts(0) = rootDict
stackKeys(0) = “ROOT”
currentDepth = 0

Do While Not fileStream.AtEndOfStream
rawLine = fileStream.ReadLine

‘ コメント行(#始まり)および空行のスキップ
line = Trim(rawLine)
If line <> “” And Left(line, 1) <> “#” Then

‘ インデントサイズ(スペース数)の計算
indentSize = Len(rawLine) – Len(LTrim(rawLine))

‘ インデント幅は可変だが、この実装では「スペースの数」をそのまま深度の目安、
‘ あるいは簡易的に2スペース/4スペースごとの階層とみなす。
‘ ここでは「インデントの増加・減少」をベースに階層を判定するロジックを構築。

‘ キーと値の分離 (Key: Value)
colonPos = InStr(line, “:”)
If colonPos > 0 Then
key = Trim(Left(line, colonPos – 1))
val = Trim(Mid(line, colonPos + 1))

‘ 階層の深さをインデントの文字数から逆算(例: 2スペース毎なら indentSize \ 2)
‘ ここでは汎用的に、現在のインデントとスタックを比較して深度を決定
Dim targetDepth
targetDepth = indentSize \ 2 ‘ 2スペースインデントを標準とする

If targetDepth > currentDepth Then
‘ 階層が深くなった場合
currentDepth = targetDepth
Set stackDicts(currentDepth) = CreateObject(“Scripting.Dictionary”)
‘ 直前のキーに対して新しいDictionaryを紐付ける
stackDicts(currentDepth – 1).Add key, stackDicts(currentDepth)
stackKeys(currentDepth) = key

ElseIf targetDepth < currentDepth Then ' 階層が浅くなった場合 currentDepth = targetDepth Set stackDicts(currentDepth) = CreateObject("Scripting.Dictionary") ' 同階層、または上位階層への紐付け If currentDepth > 0 Then
stackDicts(currentDepth – 1).Add key, stackDicts(currentDepth)
Else
rootDict.Add key, stackDicts(currentDepth)
End If
stackKeys(currentDepth) = key

Else
‘ 同一階層の場合
If currentDepth = 0 Then
If val = “” Then
Set stackDicts(0).Item(key) = CreateObject(“Scripting.Dictionary”)
Else
rootDict.Add key, val
End If
Else
If val = “” Then
Set stackDicts(currentDepth) = CreateObject(“Scripting.Dictionary”)
stackDicts(currentDepth – 1).Add key, stackDicts(currentDepth)
Else
stackDicts(currentDepth – 1).Add key, val
End If
End If
End If

‘ 値が空で、かつ子要素を持つ場合のフォールバック処理(簡易版)
If val <> “” Then
If currentDepth > 0 Then
‘ 既にAddされている場合はValueを更新するなどのハンドリングが必要だが簡略化
End If
End If

End If
End If
Loop

fileStream.Close
Set fileStream = Nothing

Set ParseIndentedConfig = rootDict
End Function

‘ ==============================================================================
‘ サブルーチン: DumpDictionary (再帰的にDictionaryの中身をイテレート)
‘ ==============================================================================
Sub DumpDictionary(dict, indent)
Dim key
For Each key In dict.Keys
If IsObject(dict.Item(key)) Then
WScript.Echo indent & key & ” (Nested Object):”
Call DumpDictionary(dict.Item(key), indent & ” “)
Else
WScript.Echo indent & key & ” = ” & dict.Item(key)
End If
Next
End Sub

—

3. シニアエンジニアが押さえるべき「実務上の極意」とアーキテクチャの急所

オブジェクトのライフサイクルとメモリ管理

VBScriptのCOMオブジェクト(特に`Scripting.Dictionary`)は、参照カウンタ方式でメモリ管理されている。多層にネストされたDictionaryを構築する場合、ルートオブジェクトを解放するだけでは不十分なケースがある。スクリプトのライフサイクル終了時には、必ずルート変数を `Nothing` に明示的に代入し、VBScriptのガベージコレクタ(厳密にはCOMの参照解放)を促すこと。数万行のログや巨大な設定ファイルをパースするバッチ処理では、このメモリ解放の規律がそのままプロセスの安定性に直結する。

インデント解析における「曖昧さ」の排除

YAMLやJSON風のテキストを扱う際、開発者が最も頭を悩ませるのは「タブ文字(`vbTab`)とスペース(`” “`)の混入」である。
実務の現場では、手動で編集された設定ファイルに半角スペースとタブが混在し、インデントの崩壊を引き起こす。
これを防ぐため、パーサの入口で必ず以下のようなタブのスペース置換(正規表現による正規化)を挟むべきだ。

Function NormalizeLine(ByVal strLine)
Dim regEx
Set regEx = New RegExp
regEx.Pattern = “^\t+”
regEx.Global = False

‘ タブを半角スペース4つ分に置換してインデント幅を統一する
If regEx.Test(strLine) Then
strLine = Replace(strLine, vbTab, ” “)
End If
Set regEx = Nothing
NormalizeLine = strLine
End Function

パフォーマンスの最適化

FSOの `OpenTextFile` は、数千行程度であれば一瞬で読み込みを完了する。しかし、ネットワーク経由の共有フォルダ上にあるファイルを直接読み込ませるような愚行は避けるべきだ。必ずローカルストレージ(例: `%TEMP%` 領域)に一度ファイルをコピーしてからFSOでストリーム処理を行うこと。I/Oのボトルネックを排除することが、レガシー環境における自動化スクリプトを「高速で信頼性の高いツール」に格上げする唯一の道である。

—

総括

VBScriptは「古い言語」として片付けられがちだが、OSの標準機能だけで完結するという圧倒的なポータビリティを持っている。今回解説したFSOとDictionaryによる再帰的パース術をマスターすれば、専用のパーサージェネレータやサードパーティ製ライブラリを一切導入できないセキュアな環境であっても、高度な構造化設定ファイルを自在に操ることができる。

レガシーの制約を嘆くのではなく、その制約の中で極限のパフォーマンスと美しさを追求することこそが、真のインフラストラクチャ・エンジニアの矜持である。

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