【テクニカル・上級編】【中級者向け】検索対象の文字列を「配列」に格納して、複数キーワードを一括置換する – Word VBA解析バイブル

スポンサーリンク

Word VBAを掌握する極限の知見:複数キーワード一括置換のアーキテクチャ設計

Word VBAにおける`Find`および`Replacement`オブジェクトの挙動は、多くの開発者が躓く最初の「ブラックボックス」である。特に、数十から数百に及ぶ用語のゆれ、社内用語の統廃合、あるいは機密情報のマスキングといった「複数キーワードの一括置換」を実装する際、場当たり的な`For`ループと`Replace`メソッドの乱用は、WordのCOMコンポーネントを疲弊させ、メモリリークと極端なパフォーマンス低下を招く。

本稿では、保守性を極限まで高めつつ、Wordの内部キャッシュとライフサイクルを完全に制御した「配列駆動型・一括置換エンジン」の設計思想を解説する。

—

1. なぜ「単体の置換コードの羅列」は地獄と化すのか

実務において、次のようなコードを見たことはないだろうか。

‘ 【アンチパターン】保守性もパフォーマンスも破綻した実装
Sub BadReplacement()
Selection.Find.ClearFormatting
Selection.Find.Replacement.ClearFormatting

Selection.Find.Text = “旧用語A”
Selection.Find.Replacement.Text = “新用語A”
Selection.Find.Execute Replace:=wdReplaceAll

Selection.Find.Text = “旧用語B”
Selection.Find.Replacement.Text = “新用語B”
Selection.Find.Execute Replace:=wdReplaceAll
‘ これが延々と続く…
End Sub

このアプローチには、シニアエンジニアの観点から見ると致命的な欠陥が3つある。

1. スコープとコンテキストの汚染: `Selection`オブジェクトに依存しているため、カーソル位置や選択範囲によって動作が不安定になり、描画更新(ScreenUpdating)のコストが毎回発生する。
2. コードの肥大化と保守性の欠如: 置換ルールが増えるたびにコード行数が直線的に増加し、ビジネスロジックとデータ(置換リスト)が完全に癒着する。
3. COM境界の跨ぎすぎ: Wordの内部エンジン(C++製)とVBA間(COM)のラウンドトリップが置換キーワードの数だけ発生し、大容量文書では数分単位の硬直を引き起こす。

この課題を解決するためには、「データを二次元配列としてメモリ上に分離」し、「`Range`オブジェクトを起点とした高速なスキャン」を行うアーキテクチャへ移行する必要がある。

—

2. アーキテクチャ設計:配列駆動型一括置換エンジン

洗練されたVBAコードとは、データと処理が完全に分離されているものである。ここでは、検索キーワードと置換文字列を格納した多次元配列をループさせ、文書全体の`Content.Find`を一括処理する設計を示す。

さらに、パフォーマンスを極限まで引き上げるため、処理前の画面描画停止、自動再計算の無効化、イベントの抑制を徹底する。

実装コード

以下のコードは、そのままモジュールに貼り付けて実行可能なプロダクション品質のエンジンである。

Option Explicit

‘ =================================================================
‘ módulo名: modBatchReplacer
‘ 概要: 2次元配列をソースとした高速一括置換エンジン
‘ =================================================================
Public Sub ExecuteAdvancedBatchReplace()
Dim startTime As Double
startTime = Timer

‘ 1. 実行環境の最適化(パフォーマンスの極限追求)
Call ToggleEnvironment(False)

On Error GoTo ErrorHandler

‘ 2. 置換マスターデータの定義(本来は外部CSVやDBからロードすることを推奨)
Dim replacementTable() As String
replacementTable = GetReplacementMaster()

‘ 3. 置換エンジンの実行
Dim processedCount As Long
processedCount = ProcessBatchReplace(ActiveDocument, replacementTable)

‘ 4. 正常終了処理
Call ToggleEnvironment(True)
MsgBox “一括置換が完了しました。” & vbCrLf & _
“処理件数: ” & processedCount & ” パターン” & vbCrLf & _
“実行時間: ” & Format(Timer – startTime, “0.00秒”), _
vbInformation, “システム通知”
Exit Sub

ErrorHandler:
‘ 異常終了時も必ず環境を復元する
Call ToggleEnvironment(True)
MsgBox “致命的なエラーが発生しました。” & vbCrLf & _
“Error: ” & Err.Description, vbCritical, “エラー”
End Sub

Private Function GetReplacementMaster() As String()
‘ 2次元配列によるマスター定義 (index 0: 検索文字, index 1: 置換文字)
‘ ※実運用ではここでADODB等を用いてCSVやSQLServerから動的ロードする
Dim master(3, 1) As String

master(0, 0) = “旧システムα”: master(0, 1) = “新システムA”
master(1, 0) = “旧システムβ”: master(1, 1) = “新システムB”
master(2, 0) = “㈱”: master(2, 1) = “(株)”
master(3, 0) = “①”: master(3, 1) = “[1]”

GetReplacementMaster = master
End Function

Private Function ProcessBatchReplace(ByVal targetDoc As Document, ByRef tbl() As String) As Long
Dim i As Long
Dim findText As String
Dim repText As String
Dim rng As Range
Dim counter As Long

‘ 文書のメインストーリーレンジを取得(ヘッダー・フッター・本文を含む場合はStoryRangesを走査すべきだが、
‘ 今回はメインコンテンツを対象とする)
Set rng = targetDoc.Content

counter = 0

‘ 配列の下限から上限までループ
For i = LBound(tbl, 1) To UBound(tbl, 1)
findText = tbl(i, 0)
repText = tbl(i, 1)

‘ Findオブジェクトのパラメータ設定
With rng.Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = findText
.Replacement.Text = repText
.Forward = True
.Wrap = wdFindStop
.Format = False
.MatchCase = True
.MatchWholeWord = False
.MatchWildcards = False

‘ 一括置換実行
If .Execute(Replace:=wdReplaceAll) Then
counter = counter + 1
End If
End With

‘ 検索範囲の崩壊を防ぐため、レンジをリセット
Set rng = targetDoc.Content
Next i

ProcessBatchReplace = counter
End Function

Private Sub ToggleEnvironment(ByVal state As Boolean)
‘ Wordの描画エンジンおよびバックグラウンド処理を制御し、速度を最大化する
With Application
.ScreenUpdating = state
.DisplayAlerts = wdAlertsNone
.Calculation = IIf(state, wdCalculationAutomatic, wdCalculationManual)
End With

If state Then
ActiveDocument.Repaginate
End If
End Sub

—

3. シニアエンジニアが押さえるべき「3つのDeep知識」

上記のコードがなぜ高速であり、なぜ実用に耐えうるのか。その背後にあるWord VBAのアーキテクチャ上の仕様を紐解く。

① `Selection` と `Range` のパフォーマンス的断絶

初心者のコードの多くは `Selection` を操作する。`Selection` は画面上のキャレット位置と同期するため、プロパティを書き換えるたびにGUIの描画ツリーが再構築される。これに対し、本コードで採用している `Range` オブジェクトは、メモリ上の純粋なテキスト範囲へのポインタに過ぎない。GUIを介さないため、処理速度は桁違いに高速化する。

② `Wrap = wdFindStop` と レンジの再アサインの必然性

Wordの `Find` メソッドは、一度ヒットして置換を行うと、内部の検索ポインタが「置換された文字列の末尾」に移動する。そのまま次の検索を続けると、文書の途中から検索が始まり、前方に見落としが発生する。
これを防ぐため、ループの各イテレーションの最後に `Set rng = targetDoc.Content` と記述し、検索レンジを強制的に文書全体の先頭にリセットしている。この一手間が、大規模文書における「置き換え漏れ」という深刻なバグを完全に根絶する。

③ 環境変数の厳密な巻き戻し (`ToggleEnvironment` の設計)

VBAで最も恐ろしいのは、エラー発生時に `ScreenUpdating = False` や `Calculation = wdCalculationManual` がそのまま残ることだ。これにより、ユーザーは「Wordがフリーズした、またはデータが消えた」と錯覚する。
`On Error GoTo ErrorHandler` を経由して、異常終了時であっても必ず環境変数が初期状態に復元される設計(RAIIパターンのVBA的解釈)を強制している点に注目してほしい。

—

4. 発展:外部システム(CSV/DB)との連携を見据えた拡張

実務の現場では、置換マスターがハードコードされていることは稀である。大抵の場合、外部のCSVファイルや、社内システムのSQL Server、あるいはExcelの特定シートから動的に読み込む必要がある。

配列(`String()`)をインターフェースとして維持しておけば、`GetReplacementMaster` 関数の内部実装をADO(ActiveX Data Objects)を用いたDBクエリ、あるいはFileSystemObjectによるCSVパーサーに差し替えるだけで、本体の置換エンジンを一切変更することなく、システム間連携アーキテクチャへと昇華させることができる。

‘ 【将来拡張のイメージ】CSVから配列へマッピングする例
Private Function LoadMasterFromCSV(ByVal csvPath As String) As String()
‘ FSO等を用いてCSVを読み込み、動的配列に格納して返す処理をここに実装する
‘ 呼び出し側(ProcessBatchReplace)からは、配列の型と構造が同じであれば完全にブラックボックスとして扱える
End Function

—

総括

文字の置換という、一見すると極めてプリミティブな処理であっても、オブジェクトのライフサイクル、メモリ空間の専有、そしてCOMコンポーネントの特性を理解した上で設計されたコードは、単なる「動くスクリプト」から「堅牢なエンタープライズ・コンポーネント」へと変貌を遂げる。

場当たり的なマクロ作成から脱却し、構造化された配列駆動型の設計を取り入れること。それこそが、レガシーとモダンが混在する現場を制する唯一の道である。

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