こんにちは! VBScriptの世界へようこそ。
業務自動化の現場で、他のシステムから吐き出されたCSVファイルを処理していて、「あれ? なんかデータの列がズレてない……?」と頭を抱えた経験はありませんか?
「よし、`Split関数`でカンマ区切りにすれば一網打尽だ!」と思って実行してみたものの、データの中に「カンマ(,)」や「改行(CR/LF)」が混ざっていて、盛大にパース(分解)が崩壊する……。これは、業務自動化エンジニアが必ず一度は通る「呪い」のようなトラップです。
今回は、Excelのマクロ記録から一歩抜け出し、VBScriptの地力を使って「ダブルクォーテーションで囲まれた厄介なカンマや改行」を完全に飼いならし、正しい二次元データ配列へと復元する極限のパース手法を伝授します。
ここをクリアすれば、あなたのVBScriptのスキルは間違いなく一段上のステージに到達しますよ。一緒にマスターしていきましょう!
—
なぜ、単純な `Split` 関数ではCSVを壊してしまうのか?
世の中の多くのCSVファイルは、RFC 4180という仕様に準拠しています。この仕様の厄介な(そして強力な)ルールが、「フィールド内にカンマや改行を含める場合、データ全体をダブルクォーテーション(`”`)で囲みなさい」というものです。
例えば、次のようなCSVがあったとします。
ID,氏名,備考
1,山田太郎,”東京都千代田区, 1-1″
2,鈴木花子,”趣味は読書です。
休日はカフェにいます。”
これを何も考えずに `Split(line, “,”)` で切ってしまうとどうなるでしょう?
1行目の住所に含まれる `,` でデータが真っ二つに割れ、2行目の改行でそもそも行の概念が崩壊します。
では、どうすればいいのでしょうか?
答えはシンプルです。「文字を1文字ずつスキャンし、今自分が『ダブルクォーテーションの中にいるのか、外にいるのか』を状態(State)として管理しながら読み進める」のです。これぞ、プログラミングの醍醐味である「ステートマシン(状態機械)解析」です。
—
文字単位ステートマシン解析の設計図
頭の中で、次のような小人(パーサー)を走らせるイメージを持ちましょう。
1. 通常モード(OutQuote): カンマに出会ったら「そこで列を区切る」。ダブルクォーテーションに出会ったら「囲みモードへ移行する」。
2. 囲みモード(InQuote): カンマや改行に出会っても「ただの文字だ!」と無視して取り込む。もう一度ダブルクォーテーションに出会ったら「通常モードへ戻る」。
この仕組みをVBScriptで実装していきます。VBScriptは文字列操作において、メモリ効率やオブジェクトのライフサイクルを正しく理解して書くことで、非常に高速に動作します。
—
実装コード:完璧なCSVパーサー
実際の業務でそのままコピー&ペーストして、今日から使える実践的なVBScriptコードを用意しました。
テキストファイル(Shift-JISやUTF-8など)を読み込み、複雑なCSVを完全な配列としてメモリ上に展開します。
‘ =================================================================0
‘ 著作権フリー・現場即戦力スクリプト: 複雑なCSVパースエンジン
‘ 対象: カンマ・改行エスケープを含むCSVを二次元配列に展開する
‘ =================================================================0
Option Explicit
Dim fso, filePath, fileStream, rawData
Dim parsedData
Dim r, c
‘ 1. ファイルシステムの準備
Set fso = CreateObject(“Scripting.FileSystemObject”)
filePath = “C:\Work\complex_data.csv” ‘ 実際のファイルパスに変更してください
If Not fso.FileExists(filePath) Then
WScript.Echo “エラー: 指定されたCSVファイルが見つかりません。” & vbCrLf & filePath
WScript.Quit
End If
‘ 2. ファイル全体の読み込み(ADO.Stream等を使うと文字コード対応も万全ですが、今回は標準のTextStreamを使用)
Set fileStream = fso.OpenTextFile(filePath, 1) ‘ 1 = ForReading
rawData = fileStream.ReadAll
fileStream.Close
Set fileStream = Nothing
‘ 3. 核心部分:ステートマシンによるCSVパース実行
parsedData = ParseCsvTo2DArray(rawData)
‘ 4. パース結果の確認(デバッグ出力)
WScript.Echo “— パース完了:行数: ” & UBound(parsedData, 1) + 1 & ” —”
For r = 0. To UBound(parsedData, 1)
‘ 各行のデータをカンマ結合してコンソールに表示
Dim rowString
rowString = “”
For c = 0 To UBound(parsedData, 2)
rowString = rowString & “[” & parsedData(r, c) & “] ”
Next
WScript.Echo “行 ” & (r + 1) & “: ” & rowString
Next
Set fso = Nothing
WScript.Quit
‘ =================================================================0
‘ 関数名: ParseCsvTo2DArray
‘ 概要: CSVの生文字列を解析し、二次元配列(行, 列)として返す
‘ =================================================================0
Function ParseCsvTo2DArray(csvText)
Dim i, length
Dim currentChar
Dim inQuotes
Dim currentField, currentRow
Dim resultList
‘ 可変長データを保持するため、VBScriptのDictionaryや擬似配列を使う手もありますが、
‘ ここではVBA/VBScriptでお馴染みの「動的配列 (ReDim)」を駆使します。
‘ 二次元配列を直接拡張するのは重いため、まずは1次元の動的配列に「行データ(1次元配列)」を格納していく
Dim rows()
Dim rowCount
rowCount = -1
Dim fields()
Dim fieldCount
fieldCount = -1
inQuotes = False
currentField = “”
length = Len(csvText)
‘ 1文字ずつスキャンするメインループ
For i = 1 To length
currentChar = Mid(csvText, i, 1)
Select Case currentChar
Case “””” ‘ ダブルクォーテーションの場合
If inQuotes And i < length Then
' エスケープされたダブルクォーテーション("")のチェック
If Mid(csvText, 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
Else
' フィールドの区切り
fieldCount = fieldCount + 1
ReDim Preserve fields(fieldCount)
fields(fieldCount) = currentField
currentField = ""
End If
Case vbCr ' キャリッジリターン (通常はそれに続くvbLfと一緒に処理するためスキップ気味に)
' 何もしない(vbLfのタイミングで改行判定を行う)
Case vbLf ' ラインフィード(改行)の場合
If inQuotes Then
' 囲みの中の改行はデータの一部として保持
currentField = currentField & vbCrLf
Else
' 行の確定
fieldCount = fieldCount + 1
ReDim Preserve fields(fieldCount)
fields(fieldCount) = currentField
currentField = ""
' 行データをリストに追加
rowCount = rowCount + 1
ReDim Preserve rows(rowCount)
rows(rowCount) = fields
' 次の行のためにフィールド配列を初期化
Erase fields
fieldCount = -1
End If
Case Else
' 通常の文字
currentField = currentField & currentChar
End Select
Next
' ファイルの末尾に改行がない場合の最後の行の処理
If currentField <> “” Or fieldCount >= 0 Then
fieldCount = fieldCount + 1
ReDim Preserve fields(fieldCount)
fields(fieldCount) = currentField
rowCount = rowCount + 1
ReDim Preserve rows(rowCount)
rows(rowCount) = fields
End If
‘ —————————————————————-0
‘ 収集した1次元の行リスト(rows)を、綺麗な「二次元配列(Row, Col)」に整形する
‘ —————————————————————-0
If rowCount < 0 Then
' 空データの場合のフォールバック
Dim emptyArr(0, 0)
emptyArr(0, 0) = ""
ParseCsvTo2DArray = emptyArr
Exit Function
End If
' 最大列数を特定する(不整形CSVへの耐性)
Dim maxCols
maxCols = 0
For i = 0 To rowCount
If UBound(rows(i)) > maxCols Then
maxCols = UBound(rows(i))
End If
Next
‘ 最終的な二次元配列を定義
Dim finalMatrix()
ReDim finalMatrix(rowCount, maxCols)
Dim rIdx, cIdx
For rIdx = 0 To rowCount
Dim tempFields
tempFields = rows(rIdx)
For cIdx = 0 To UBound(tempFields)
finalMatrix(rIdx, cIdx) = tempFields(cIdx)
Next
‘ 足りない列がある場合は空文字で埋める
For cIdx = UBound(tempFields) + 1 To maxCols
finalMatrix(rIdx, cIdx) = “”
Next
Next
ParseCsvTo2DArray = finalMatrix
End Function
—
コードの注目ポイント:エンジニアのこだわり
1. `ReDim Preserve` のスマートな活用
VBScriptでは配列のサイズを後から変更する際に `ReDim Preserve` を使いますが、これを毎回やるとパフォーマンスが落ちます。しかし、今回のステートマシン解析においては、フィールド単位・行単位の動的拡張が必須です。コード内では行と列を分離して管理することで、メモリの無駄な消費を最小限に抑えています。
2. 「`””`(エスケープされたダブルクォーテーション)」への完全対応
CSVの仕様上、データの中にダブルクォーテーション自体を含めたい場合は、二重(`””`)にして表現します。このコードでは `Mid(csvText, i + 1, 1) = “”””` のロジックにより、それを正しく1文字のダブルクォーテーションとして復元する堅牢性を備えています。
3. 不整形なCSVへの耐性(最大列数の自動検出)
世の中のシステムが吐き出すCSVは、必ずしもすべての行の列数が一致しているとは限りません。途中でデータが途切れている行があっても、配列のインデックスエラー(境界超えエラー)を起こさないよう、最後に `maxCols` を算出して長方形の綺麗なお弁当箱(二次元配列)にパディング(穴埋め)する設計にしています。
—
陥りやすいエラーと回避のコツ
- 「メモリ不足(Out of memory)」エラーが出る
- 原因: 数十MB〜数百MBある巨大なCSVファイルを `ReadAll` で一気にメモリに読み込もうとすると、VBScript(32bitプロセス)のメモリ制限に引っかかります。
- 対策: 巨大なファイルの場合は、今回のような一括読み込みではなく、`OpenTextFile` の `ReadLine` を使って1行ずつステートマシンに流し込む「ストリーム処理型」に書き換える必要があります。(※ただし行内改行を含む場合は `ReadLine` だけでは不十分なので、バッファリングが必要です。今回は通常の業務で扱う数千行程度のCSVを想定しています)
- 文字化けが発生する
- 原因: Windows標準の `Scripting.FileSystemObject` は、デフォルトのテキスト読み込みでANSI(Shift-JIS等)を前提とします。相手システムが「UTF-8 (BOMなし)」で出力している場合、日本語が文字化けします。
- 対策: UTF-8のCSVを扱う場合は、FSOではなく `ADODB.Stream` オブジェクトを使用して `Charset = “UTF-8″` を指定して読み込んでください。
—
おわりに
いかがでしたでしょうか?
「VBScriptなんて古い言語だ」と言われることもありますが、Windows環境があれば特別なインストールも不要で、今日書いたコードが明日から会社のサーバーで動き出す――この手軽さと即効性は、他のどの言語にも代えがたい魅力があります。
そして、今回解説したような「文字を1文字ずつ読み解いて状態を管理する」というアルゴリズム的アプローチは、VBScriptだけでなく、Python、JavaScript、C#など、どんな言語に進んでも一生モノの武器になります。
ここをクリアしたあなたなら、どんなに汚いレガシーなCSVデータが来ても、もう恐れるものはありません。ぜひ現場の自動化ツールに組み込んで、定時退社を勝ち取ってくださいね!
