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

スポンサーリンク

こんにちは! VBScriptの世界へようこそ。
業務自動化の現場で、Excelマクロ(VBA)から一歩進んでバッチ処理やWindowsのタスク自動化を任されるようになると、避けて通れないのが「VBScriptによるテキスト処理」ですよね。

「外部の便利なパーサーライブラリなんて使えない閉じた環境で、階層構造を持つ設定ファイルを読み込まなければならない……」
そんな絶望的な状況に出会ったことはありませんか? JSONやYAMLのようなインデント構造を持つテキストを、サードパーティ製ツールなしで自力パースする。これができれば、あなたも立派なVBScriptの職人(アーキテクト)です。

今回は、FSO(FileSystemObject)と再帰処理、そしてDictionaryオブジェクトを駆使して、インデント構造を持つテキストをネストされたDictionary(連想配列)に変換するパース術を伝授します。ここをクリアすれば、VBScriptの基本とデータ構造の扱いはもうバッチリですよ!

—

なぜFSOと再帰処理なのか?

業務の現場では、次のようなインデント(空白数)で親子関係を表現した設定ファイルを扱うことがよくあります。

server:
host: 192.168.1.10
port: 8080
database:
name: production_db
user: admin

これをVBScriptで読み解くとき、「何段階ネストしているか分からない」「何行続くか分からない」という壁にぶつかります。
ここで、外部のJSONパーサーなどが使えない環境下において、FSOで1行ずつテキストを読み込み、インデントの深さを計算しながら「再帰関数(自分自身を呼び出す関数)」でツリー構造を組み立てていくアプローチが非常に強力な武器になります。

—

押さえておくべき3つの重要パーツ

今回のコードを組み上げるために、以下の3つの要素をマスターします。

1. `Scripting.FileSystemObject` (FSO): ファイルを1行ずつ安全に読み込むための必須オブジェクト。
2. `Scripting.Dictionary`: キーと値をペアで保持する連想配列。これのネスト(入れ子)構造でJSON風データを表現します。
3. インデントの深さの測定: 行頭の半角スペース(またはタブ)の数を数え、現在の階層(レベル)を判定するロジック。

—

実装コード:インデントパーサーの全貌

それでは、現場でそのままコピペして使える実用スクリプトを公開します。
同じフォルダに `config.txt` という名前で先ほどのテキストを配置し、このスクリプトを実行してみてください。

‘ =================================================================0
‘ 階層構造テキストを解析し、Dictionary化するVBScriptサンプル
‘ =================================================================0
Option Explicit

Sub Main()
Dim fso, filePath
Set fso = CreateObject(“Scripting.FileSystemObject”)

‘ スクリプトと同じフォルダにある設定ファイルを指定
filePath = fso.GetParentFolderName(WScript.ScriptFullName) & “\config.txt”

If Not fso.FileExists(filePath) Then
WScript.Echo “エラー: 設定ファイルが見つかりません -> ” & filePath
Exit Sub
End If

‘ ファイルを開く (ForReading = 1)
Dim ts
Set ts = fso.OpenTextFile(filePath, 1, False)

‘ 全行を配列として取得し、メモリ上で効率よく処理する
Dim allLines()
Dim lineCount
lineCount = 0

Do While Not ts.AtEndOfStream
ReDim Preserve allLines(lineCount)
allLines(lineCount) = ts.ReadLine
lineCount = lineCount + 1
Loop
ts.Close

‘ パース処理の開始(グローバルインデックスを 0 からスタート)
Dim globalIndex
globalIndex = 0

Dim configDict
Set configDict = ParseIndentedText(allLines, 0, globalIndex)

‘ 結果の確認(再帰的に中身を表示)
WScript.Echo “— パース結果の確認 —”
PrintDictionary configDict, “”

‘ オブジェクトの解放
Set ts = Nothing
Set fso = Nothing
End Sub

‘ —————————————————————-1
‘ 再帰的にインデント構造を解析してDictionaryを構築する関数
‘ —————————————————————-2
Function ParseIndentedText(lines, currentIndent, ByRef index)
Dim dict
Set dict = CreateObject(“Scripting.Dictionary”)

Dim totalLines
totalLines = UBound(lines) + 1

Do While index < totalLines Dim currentLine currentLine = lines(index) ' 空行やコメント行(#で始まる行)はスキップ If Trim(currentLine) = "" Or Left(Trim(currentLine), 1) = "#" Then index = index + 1 Else ' インデントの深さ(先頭のスペースの数)を算出 Dim indentLen indentLen = Len(currentLine) - Len(LTrim(currentLine)) ' 親のインデントより浅くなったら、この階層の処理を抜ける(上の階層に戻る) If indentLen < currentIndent Then Exit Do End If ' キーと値に分解(コロン「:」で分割) Dim trimmedLine, colonPos, key, val trimmedLine = Trim(currentLine) colonPos = InStr(trimmedLine, ":") If colonPos > 0 Then
key = Trim(Left(trimmedLine, colonPos – 1))
val = Trim(Mid(trimmedLine, colonPos + 1))

‘ 値が空の場合、次の行以降に子階層(ネスト)が存在するとみなす
If val = “” Then
index = index + 1 ‘ 次の行へ進む
‘ 自分より深いインデントを持つ子要素を再帰的に取得
Set dict(key) = ParseIndentedText(lines, indentLen + 1, index)
Else
‘ 通常のキーバリュー
dict(key) = val
index = index + 1 ‘ 次の行へ進む
End If
Else
‘ コロンがない不正な行のフォールバック
index = index + 1
End If
End If
Loop

Set ParseIndentedText = dict
End Function

‘ —————————————————————-3
‘ ディクショナリの内容を再帰的に表示するデバッグ用サブルーチン
‘ —————————————————————-4
Sub PrintDictionary(d, indent)
Dim k
For Each k In d.Keys
If IsObject(d(k)) Then
WScript.Echo indent & k & ” (Container):”
PrintDictionary d(k), indent & ” ”
Else
WScript.Echo indent & k & ” = ” & d(k)
End If
Next
End Sub

‘ 実行のトリガー
Main()

—

コードのここがポイント! 押さえておきたい技術的知見

1. `ByRef` を使ったグローバルインデックスの共有

再帰処理において最も頭を悩ませるのが、「今、何行目を読み込んでいるか(どこまで処理したか)」の現在地をどう保持するかです。
ここでは、関数引数に `ByRef index` を指定し、再帰呼び出しを抜けた後も読み込み位置のポインタがインクリメントされた状態で親へ引き継がれるように工夫しています。これがバグを生みにくくするエレガントな設計のコツです。

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

VBScriptの `Scripting.Dictionary` をネストさせる際、`Set dict(key) = subDict` のようにオブジェクトそのものを別のDictionaryの要素として格納できます。
ここで注意すべきは、VBScriptは明示的にオブジェクトを破棄(`Set obj = Nothing`)しなくてもスコープを抜ければ自動でメモリ解放されますが、巨大なテキストを扱う場合は不要になった参照を早めに切る意識を持つとパフォーマンスが安定します。

—

陥りやすいエラーと対策

1. 「インデントのスペース数が混在している」エラー

  • 原因: ある行は半角スペース2個、別の行はタブ文字(Tab)や半角スペース4個などが混ざっていると、`indentLen` の計算が狂います。
  • 対策: 設定ファイルを作成する際は、「インデントは必ず半角スペース2個または4個で統一する(タブを使わない)」というルールを運用上徹底してください。必要であれば、コード内で `Replace(currentLine, vbTab, ” “)` のようにタブをスペースに置き換えてから長さを測る前処理を入れると、より頑健(ロバスト)になります。

2. 型 mismatch エラー (`Type mismatch`)

  • 原因: `PrintDictionary` などのデバッグ関数で、値がDictionaryオブジェクトであるにもかかわらず通常の文字列として扱おうとしたときに発生します。
  • 対策: `IsObject(d(k))` を用いて、中身がさらに別のDictionary(コンテナ)なのか、文字列なのかを必ず条件分岐で判定してください。

—

まとめ

いかがでしたでしょうか?
今回は、FSOと再帰処理、そしてDictionaryを組み合わせて、VBScriptの標準機能だけでインデント構造をパースする極意をお伝えしました。

「VBScriptは古い言語だから大したことはできない」なんて思っていませんでしたか?
このように基本原理(ポインタの概念や再帰的思考)を正しく理解していれば、外部ライブラリが一切使えない制限の厳しいセキュアな環境であっても、高度なテキスト処理をいとも簡単に実装できるようになります。

ここをクリアしたあなたなら、もうVBScriptの基礎はバッチリです! ぜひ日々の業務の自動化ツールに応用してみてくださいね。先輩エンジニアとして、あなたのスキルアップを心から応援しています!

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