こんにちは!VBScriptの世界へようこそ。
業務自動化の現場で、ExcelマクロやPowerShellでは大げさすぎるけれど、サクッとWindows環境で動く自動化ツールを作りたい……そんな時に頼りになるのが「VBScript(WSH)」です。
さて、業務で日常茶飯事に出現する「CSVファイル」。
「なんだ、`Split`関数でカンマごとにパチパチッと分割すれば終わりでしょ?」
――もしあなたがそう思っているなら、今日のこの記事は、あなたのエンジニア人生の大きなターニングポイントになるでしょう。
現実は甘くありません。実務で送られてくるCSVには、「ダブルクォーテーション(`”`)で囲まれた中に、カンマや改行が含まれている」という、極悪非道な変則データが混ざっています。これを素朴な `Split` で処理すると、データが途中で無残に分断され、データベースやExcelが盛大にエラーを吐き出すことになります。
今回は、VBScriptのプリミティブなパワーを極限まで引き出し、どんなに複雑な変則CSVをも正確無比にねじ伏せる「文字単位ステートマシン(状態機械)パースアルゴリズム」を伝授します。
ここをクリアすれば、あなたのVBScriptの基礎力は本物のプログラミングの域に達しますよ。さあ、一緒に扉を開けましょう!
—
1. なぜ単純な `Split` ではCSVパースに失敗するのか?
まずは敵を知ることから始めましょう。以下のようなCSVデータがあったとします。
“ID”,”名前”,”備考”
1,”山田 太郎”,”東京本社, 第一営業部所属
特記事項:特になし”
2,”佐藤 花子”,”大阪支店”
これを単純に `Line` ごとに読み込んで `Split(line, “,”)` で切断するとどうなるでしょうか?
2行目のデータを見てください。`”東京本社, 第一営業部所属` の中にあるカンマで、カラムが無理やり引き裂かれます。さらに、その下の改行コードで「次の行のデータ」と誤認されてしまいます。
結果、データ行数が狂い、列の位置がズレて、業務システムは大混乱。夜間バッチなら大チョンボです。
解決のアプローチ:1文字ずつ「状態」を管理する
この問題を解決するためには、ファイル全体を「1文字ずつ」スキャンし、「いま私はダブルクォーテーションの中にいるのか? 外にいるのか?」という状態(State)を記憶しながら読み進める必要があります。
これを実現するのが、今回構築する自作アルゴリズムです。
—
2. 【実践】変則CSV高精度パース・VBScriptモジュール
実際の現場でそのままコピペして動かせる、完成されたVBScriptコードを提示します。
デスクトップなどに `parse_csv.vbs` という名前で保存し、文字コードを `Shift-JIS`(またはBOM付きUTF-8)にして実行してみてください。
‘ ==============================================================================
‘ スクリプト名: AdvancedCSVParser.vbs
‘ 概要: ダブルクォーテーション内の改行・カンマを完璧にハンドリングするCSVパーサー
‘ 著者: チーフアーキテクト
‘ ==============================================================================
Option Explicit
Sub Main()
Dim fso, filePath, fileStream, fileContent
filePath = “sample.csv” ‘ 読み込むCSVファイルのパス
Set fso = CreateObject(“Scripting.FileSystemObject”)
‘ ファイルが存在するかチェック
If Not fso.FileExists(filePath) Then
MsgBox “対象のCSVファイルが見つかりません: ” & filePath, vbCritical, “エラー”
Exit Sub
End If
‘ ファイル全体をテキストとして一括読み込み(改行を含む文字列ブロックとして扱うため)
Set fileStream = fso.OpenTextFile(filePath, 1, False) ‘ 1 = ForReading
fileContent = fileStream.ReadAll
fileStream.Close
Set fileStream = Nothing
‘ 自作パース関数の呼び出し(二次元配列が返ってくる)
Dim parsedData
parsedData = ParseComplexCSV(fileContent)
‘ 動作確認:パース結果をログに出力してみる
Dim r, c, rowCount, colCount
rowCount = UBound(parsedData, 1)
colCount = UBound(parsedData, 2)
Dim msg
msg = “パース完了! 総行数: ” & (rowCount + 1) & vbCrLf & vbCrLf
‘ サとして最初の3行を表示
For r = 0 To IIf(rowCount > 2, 2, rowCount)
For c = 0 To colCount
msg = msg & “[” & parsedData(r, c) & “] ”
Next
msg = msg & vbCrLf
Next
MsgBox msg, vbInformation, “パース結果プレビュー”
Set fso = Nothing
End Sub
‘ ==============================================================================
‘ 関数名: ParseComplexCSV
‘ 引数: rawText (String) – CSVファイルの全テキスト
‘ 戻り値: 2次元Variant配列 (Row, Col)
‘ ==============================================================================
Function ParseComplexCSV(ByVal rawText)
Dim i, length
Dim currentChar
Dim inQuotes ‘ Boolean: 現在ダブルクォーテーションの中にいるか?
Dim currentField ‘ String: 現在ビルド中のフィールド文字列
Dim currentRow ‘ 1行分のデータを格納する動的配列 (Listの代わり)
Dim tempRows() ‘ 全行を一時的に格納する動的配列
inQuotes = False
currentField = “”
‘ 動的配列の初期化
ReDim currentRow(0)
ReDim tempRows(0)
Dim rowIdx: rowIdx = 0
Dim colIdx: colIdx = 0
length = Len(rawText)
‘ 1文字ずつ走査するメインループ(ステートマシン)
For i = 1 To length
currentChar = Mid(rawText, i, 1)
Select Case currentChar
Case “””” ‘ ダブルクォーテーションの場合
If inQuotes And i < length Then
' エスケープされたダブルクォーテーション("")の判定
If Mid(rawText, i + 1, 1) = """" Then
currentField = currentField & """"
i = i + 1 ' 次の文字をスキップ
Else
inQuotes = False
End If
Else
inQuotes = True
End If
Case "," ' カンマの場合
If inQuotes Then
' クォーテーション内のカンマは単なる文字として扱う
currentField = currentField & currentChar
End If
' クォーテーション外のカンマは「列の区切り」
ReDim Preserve currentRow(colIdx)
currentRow(colIdx) = currentField
currentField = ""
colIdx = colIdx + 1
End If
Case vbCr, vbLf ' 改行コードの場合(CRまたはLF)
' Windowsの改行(CR+LF)の重複カウントを防ぐため、直後のLFを飲み込む処理
If currentChar = vbCr And i < length Then
If Mid(rawText, i + 1, 1) = vbLf Then
i = i + 1
End If
End If
If inQuotes Then
' クォーテーション内の改行は「データ内の改行」としてそのまま保持
currentField = currentField & vbCrLf
Else
' クォーテーション外の改行は「行の区切り」
ReDim Preserve currentRow(colIdx)
currentRow(colIdx) = currentField
' tempRowsに現在の行を格納
If rowIdx > 0 Then ReDim Preserve tempRows(rowIdx)
tempRows(rowIdx) = currentRow
‘ 次の行のために変数をリセット
rowIdx = rowIdx + 1
colIdx = 0
currentField = “”
ReDim currentRow(0)
End If
Case Else
‘ その他の文字はそのままフィールドに追加
currentField = currentField & currentChar
End Select
Next
‘ ファイル末尾に改行がない場合の最終行の処理
If Len(currentField) > 0 Or colIdx > 0 Then
ReDim Preserve currentRow(colIdx)
currentRow(colIdx) = currentField
If rowIdx > 0 Then ReDim Preserve tempRows(rowIdx)
tempRows(rowIdx) = currentRow
End If
‘ — ここからジャグ配列(行ごとに長さが違う可能性のある配列)を綺麗なお作法の2次元配列に整形 —
Dim maxCols: maxCols = 0
For i = 0 UBound(tempRows)
If IsArray(tempRows(i)) Then
If UBound(tempRows(i)) > maxCols Then
maxCols = UBound(tempRows(i))
End If
End If
Next
Dim finalResult()
ReDim finalResult(UBound(tempRows), maxCols)
Dim r, c
For r = 0 To UBound(tempRows)
If IsArray(tempRows(r)) Then
For c = 0 To UBound(tempRows(r))
finalResult(r, c) = tempRows(r)(c)
Next
End If
Next
ParseComplexCSV = finalResult
End Function
‘ 簡易的なIIf関数の定義
Function IIf(expr, truePart, falsePart)
If expr Then IIf = truePart Else IIf = falsePart
End Function
‘ 処理の実行
Main()
—
3. コードの心臓部を解説:ここがVBScriptの真髄
プログラミング初学者の方や、マクロの記録からステップアップしたい方が「おっ」と唸るポイントを解説します。
① `Option Explicit` はエンジニアの生命線
コードの最上部に書かれている `Option Explicit`。これ、絶対に省略してはいけません。
VBScriptはデフォルトでは、変数宣言をしなくてもタイポ(スペルミス)しただけで勝手に新しい変数が作られてしまいます。これが原因でデバッグに何時間も溶かすことになります。この宣言を入れることで、「すべての変数は明示的に宣言しなければならない」という規律を生み、バグを未然に防ぎます。
② ステートマシン(状態機械)の概念
今回のパースの肝は、変数 `inQuotes`(Boolean型)です。
- `inQuotes = False` のときは、カンマ `,` は「列の区切り」、改行は「行の区切り」として厳格に扱います。
- 一度ダブルクォーテーション `”` を検知して `inQuotes = True` になると、その中に含まれる `,` や `vbCrLf` はすべて「ただの文字列」として、変数 `currentField` に飲み込まれていきます。
この「モードの切り替え」を行うことで、どれほど複雑に入り組んだテキストでも、正確に分解できるのです。
③ VBScriptにおける「動的配列(`ReDim Preserve`)」の重み
VBScriptには、C#の `List
そのため、`ReDim Preserve` という命令を使って、配列のサイズを拡張しながらデータを詰め込んでいきます。
ただし、パフォーマンスの観点から言えば、巨大なファイルに対して何万回も `ReDim Preserve` を呼ぶのは避けるべきです(メモリの再割り当てコストが高いため)。今回のコードのように、中規模程度のCSVであれば十分に実用的な速度で動作します。
—
4. 陥りやすい罠とエラー対策
現場でこのスクリプトを運用する際によくあるトラブルと対策をまとめました。
1. 文字コードの罠 (Shift-JIS vs UTF-8)
- VBScriptの `FileSystemObject` は、デフォルトではANSI(Shift-JISなど)でファイルを読み込みます。もしUTF-8(BOMなし)のCSVを読み込むと、日本語が盛大に文字化けします。
- 対策: 読み込むCSVは事前にExcelなどで開いて「CSV (コンマ区切り)」で保存し直すか、FSOの代わりに `ADODB.Stream` を使って文字コードを指定して読み込むようにチューニングしてください。
2. ダブルクォーテーションのエスケープ (`””`)
- CSV規格では、データの中にダブルクォーテーションを入れたい場合、`””` のように二重にして表現します。今回のコードでは `Mid(rawText, i + 1, 1) = “”””` のロジックでこれを安全に回避(エスケープ解除)しています。ここを見落とすパーサーが世の中には多すぎるので、自作モジュールの強みとなります。
—
まとめ
いかがでしたか?
「たかがCSV、されどCSV」。単純な文字列操作に見えて、実はコンパイラや字句解析(Lexer)の基礎技術がギュッと詰まった非常に奥深いテーマです。
ここをクリアできたあなたなら、VBScriptの基本的な制御構文、条件分岐、そして配列の概念を完全にマスターしたと言って過準ありません。マクロの自動記録の枠を飛び出し、自分の手でロジックを組み上げるエンジニアとしての自信を持ってくださいね。
日々の面倒な業務を、あなたの手でスマートに自動化していきましょう。それでは、次回の実践的な知見もお楽しみに!
