【テクニカル・上級編】【初心者向け】ワイルドカード検索で「特定の記号で囲まれた文字列」を抽出して別ファイルに保存する – Word VBA解析バイブル

スポンサーリンク

Word VBAを掌握する極限の知見:ワイルドカード検索とメモリ管理の極意

Word VBAにおける`Find`および`Replacement`オブジェクトは、その柔軟性の裏腹に、COMのライフサイクルやWord固有のカーソル(Selection / Range)の挙動を深く理解していないと、メモリリークや意図しない文書の破壊を招く「諸刃の剣」である。

今回は、シニアエンジニアや社内システム管理者が直面する実務要件として、「特定の記号で囲まれた文字列をワイルドカードで効率的に抽出し、別ファイルにスプール(保存)する」というタスクを題材に、妥協のないアーキテクチャを提示する。

—

1. なぜ「Selection」を使ってはならないのか?

多くの解説書では `Selection.Find` が推奨されるが、チーフアーキテクトの視点から言えば、`Selection` を使うコードはレガシーの悪臭を放つバグ温床でしかない。

画面描画(UIの更新)を伴う `Selection` 操作は圧倒的に遅く、さらにドキュメントのスクロールやフォーカスの喪失によって予期せぬ例外を引き起こす。我々が使うべきは、メモリ上で完結する `Range.Find` である。

さらに、Wordの `Find` オブジェクトのパラメータは、一度設定すると次に呼び出すまで状態(State)を保持し続ける。つまり、明示的な初期化を行わないコードは、前回の実行時のゴミを引き継いで暴走する。この点を踏まえたプロパティのクリアと設定が、堅牢なシステムの絶対条件となる。

—

2. ワイルドカード検索の罠と仕様

Wordのワイルドカードエンジンは、VBAの標準正規表現(VBScript.RegExp)とも、.NETのRegexとも異なる、独自の遺物のような仕様を持っている。

  • “ : 任意の文字列(0文字以上)
  • `?` : 任意の1文字
  • `[!a-z]` : 指定文字以外の1文字(否定)
  • `<>` : 単語の先頭/末尾

例えば、「【」から「】」までの文字列を抽出する場合、単純に `【】` と指定すると、文書中に「【A】 ほげほげ 【B】」とあった場合、最初の「【」から最後の「】」までを一塊として捉えてしまう(最長一致:Greedy Matchの暴走)。

これを回避するため、文字クラスの否定(`[! ]`など)を組み合わせるか、あるいはループ内で適切にRangeを縮退させるアルゴリズム的アプローチが必要となる。

—

3. 実装コード:堅牢性と速度を極めた抽出エンジン

以下に、実務の現場でそのまま稼働するプロダクション品質のVBAコードを提示する。エラーハンドリング、オブジェクトの即時解放、画面描画の抑止(ScreenUpdating)を完備している。

Option Explicit

‘ ==============================================================================
‘ 処理名: 囲み文字抽出・別ファイル保存エンジン
‘ 概要: 指定された囲み文字(例: 【 と 】)の中身を検索し、新規ドキュメントへ出力する
‘ ==============================================================================
Sub ExtractEnclosedTextToNewDocument()
‘ 画面描画とアラートを完全抑制し、実行速度を極限まで引き上げる
Application.ScreenUpdating = False
Application.DisplayAlerts = wdAlertsNone

Dim srcDoc As Document
Set srcDoc = ActiveDocument

‘ 新規ドキュメントの作成(テンプレートなし、標準文書)
Dim outDoc As Document
Set outDoc = Documents.Add(Visible:=False)

Dim targetRange As Range
Set targetRange = srcDoc.Content

‘ Findオブジェクトの構成
With targetRange.Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = “【】” ‘ 抽出したいパターン(必要に応じて変更)
.MatchWildcards = True
.Forward = True
.Wrap = wdFindStop
.MatchCase = True
.MatchWholeWord = False
End With

Dim extractedCount As Long
extractedCount = 0

‘ 検索ループの実行
Do While targetRange.Find.Execute
‘ 冗長なマッチングや余分な空白の排除などのバリデーションをここに挟む
If Len(targetRange.Text) > 2 Then

‘ 出力用ドキュメントに書き出し
outDoc.Content.InsertAfter targetRange.Text & vbCrLf
extractedCount = extractedCount + 1

End If

‘ 次の検索へ進むため、Rangeの終端へポインタを移動(無限ループ防止)
targetRange.Collapse wdCollapseEnd
Loop

‘ 抽出結果が存在する場合のみファイルを保存
If extractedCount > 0 Then
Dim savePath As String
savePath = srcDoc.Path & “\Extracted_Result_” & Format(Now, “yyyymmdd_HHNNSS”) & “.docx”

outDoc.SaveAs2 FileName:=savePath, FileFormat:=wdFormatDocumentDefault
outDoc.Close SaveChanges:=False

MsgBox “抽出完了:” & extractedCount & ” 件のデータを保存しました。” & vbCrLf & “保存先: ” & savePath, vbInformation, “アーキテクチャ通知”
Else
outDoc.Close SaveChanges:=False
MsgBox “条件に一致する文字列は見つかりませんでした。”, vbExclamation, “アーキテクチャ通知”
End If

CleanUp:
‘ オブジェクトの明示的解放(メモリリークの根絶)
Set targetRange = Nothing
Set outDoc = Nothing
Set srcDoc = Nothing

‘ 環境の復元
Application.ScreenUpdating = True
Application.DisplayAlerts = wdAlertsAll
Exit Sub

ErrorHandler:
MsgBox “致命的なエラーが発生しました: ” & Err.Description, vbCritical, “System Error”
Resume CleanUp
End Sub

—

4. チーフアーキテクトからの技術的助言

1. メモリ管理の徹底:
VBAのガベージコレクションは頼りにならない。特にCOMオブジェクト(`Document`, `Range`, `Find` 等)は、スコープを抜ける前に `Set xxx = Nothing` によって参照カウンタを即座にデクリメントさせることが、長時間のバッチ処理やループ実行におけるメモリリークを防ぐ唯一の現実的な手法である。
2. 無限ループの罠:
`Range.Find.Execute` を使う際、マッチした後に `targetRange.Collapse wdCollapseEnd` を記述し忘れると、同一箇所を無限に指し続け、VBAがフリーズする(CPU使用率100%)。ワイルドカード検索における `Collapse` の位置は厳密に設計しなければならない。
3. システム間連携への拡張:
今回は結果をWord文書として保存したが、社内基幹システムやRPAとの連携を見据える場合、`outDoc.SaveAs2` のフォーマットを `wdFormatXML` や `wdFormatText` に変更し、CSV/JSONライクなストリームとして吐き出す設計に拡張することも容易だ。

レガシーなツールと侮られがちなWord VBAであるが、背後にあるオブジェクトモデルの挙動を完全に掌握すれば、堅牢でエンタープライズな自動化ソリューションへと昇華させることができる。現場の生産性を限界まで引き上げるエンジニアリングを、是非あなたのコードにも実装してほしい。

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