【Word VBA】ヘッダー・フッターを取りこぼすな!文書全体を完全走査するロバストな置換エンジンの設計
Word VBAで `Find` オブジェクトを使った置換処理を実装した際、こんな経験はないだろうか?
「本文中の文字列は完璧に置換できた。しかし、納品後にクライアントから『ヘッダーの社名が古いままですよ』と指摘された……」
多くの開発者が陥るこの罠。原因は明確だ。Wordの `ActiveDocument.Content.Find` は、文字通り「本文(Main Story)」しか見ていない。セクションごとに独立したヘッダーやフッター、さらにはテキストボックス内部などは、デフォルトの検索スコープの圏外なのだ。
プロの業務自動化エンジニアとして言おう。「文書の一部しか検索できない置換ツールは、実務においてはバグと同義」である。
今回は、セクションの概念とストーリーの繋がりを完全理解し、文書内のあらゆるテキストを漏らさず狩り尽くす「真に堅牢なヘッダー・フッター置換アルゴリズム」を伝授する。
—
なぜ通常の `Find` ではヘッダーに届かないのか?
Wordのドキュメント構造は、私たちが画面で見ている「1冊の本」のような単純な連続体ではない。内部的には「ストーリー(Story)」と呼ばれる独立したテキスト空間の集合体として構築されている。
- 本文(wdMainTextStory)
- ヘッダー・フッター(wdPrimaryHeader, wdFirstPageHeader, wdEvenPagesHeader など)
- コメント、脚注、テキストボックス など
これらはそれぞれが独立した `Range` を持っている。したがって、文書全体を書き換えるためには、すべてのセクションをループし、さらに各セクションが持つ複数のヘッダー・フッターのストーリーを個別に対象として `Find` を実行しなければならない。
—
バグを生まない堅牢な設計アプローチ
実務で使えるレベルの置換エンジンを構築するためには、以下の3点を担保する必要がある。
1. セクションの独立性とリンク(LinkToPrevious)の考慮
前セクションと同じヘッダーを使用している場合、重複して置換処理を行うとエラーや無駄な負荷の原因になる。しかし、ストーリー単位でアクセスすればWordがよしなに処理してくれるため、基本はすべてのセクションストーリーを走査対象とする。
2. 置換条件(Find)の確実なリセット
`Find` プロパティは一度設定すると前回の状態を保持してしまう(モーダルな挙動を引きずる)。処理の前後で必ず `.ClearFormatting` と `.Replacement.ClearFormatting` を実行すること。
3. 無限ループの防止
`.Execute Replace:=wdReplaceAll` を使用する場合でも、ストーリーごとの確実なスコープ管理が不可欠。
—
【プロダクションコード】文書全体(本文+全ヘッダー・フッター)完全置換マクロ
以下のコードは、実務の現場でそのままコピー&ペーストして組み込める、プロダクション品質のVBAモジュールだ。エラーハンドリングとオブジェクトの解放にも配慮している。
Option Explicit
‘ ==============================================================================
‘ 処理名 : ReplaceTextInAllStories
‘ 概要 : 本文およびすべてのセクションのヘッダー・フッターを網羅し、
指定した文字列を完全に置換する
‘ ==============================================================================
Public Sub ExecuteFullDocumentReplacement()
Dim targetDoc As Document
Set targetDoc = ActiveDocument ‘ 処理対象ドキュメント(必要に応じて変更可能)
Dim targetFindText As String
Dim targetReplaceText As String
‘ 【実務設定】検索ワードと置換ワード
targetFindText = “【旧会社名】”
targetReplaceText = “【新会社名株式会社】”
On Error GoTo ErrorHandler
‘ 画面描画を停止し、処理速度を劇的に向上させる(プロの必須テクニック)
Application.ScreenUpdating = False
Application.DisplayAlerts = wdAlertsNone
Dim counter As Long
counter = 0
‘ 1. 本文(Main Story)の置換を実行
counter = counter + ReplaceInStory(targetDoc.Content, targetFindText, targetReplaceText)
‘ 2. すべてのセクションのヘッダー・フッターを走査
Dim sec As Section
Dim headerType As Variant
Dim footerType As Variant
‘ 扱うヘッダー・フッターの種類を配列で定義
‘ (先頭ページ違い、奇数偶数違いのすべてを網羅)
Dim headerTypes As Variant
headerTypes = Array(wdHeaderFooterFirstPage, wdHeaderFooterEvenPages, wdHeaderFooterPrimary)
For Each sec In targetDoc.Sections
‘ ヘッダーの走査
For Each headerType In headerTypes
If Not sec.Headers(headerType).Range Is Nothing Then
counter = counter + ReplaceInStory(sec.Headers(headerType).Range, targetFindText, targetReplaceText)
End If
Next headerType
‘ フッターの走査
For Each footerType In headerTypes
If Not sec.Footers(footerType).Range Is Nothing Then
counter = counter + ReplaceInStory(sec.Footers(footerType).Range, targetFindText, targetReplaceText)
End If
Next footerType
Next sec
‘ 処理完了の通知
Application.ScreenUpdating = True
Application.DisplayAlerts = wdAlertsAll
MsgBox “置換処理が完了しました。” & vbCrLf & _
“置換対象の総ストーリー数/箇所: ” & counter & ” 箇所”, vbInformation, “完了”
Exit Sub
ErrorHandler:
‘ 異常終了時のリカバリ
Application.ScreenUpdating = True
Application.DisplayAlerts = wdAlertsAll
MsgBox “予期せぬエラーが発生しました。” & vbCrLf & _
“Error: ” & Err.Description, vbCritical, “エラー”
End Sub
‘ ==============================================================================
‘ 内部関数 : 個別のRange(Story)に対してFind/Replaceを実行する
‘ ==============================================================================
Private Function ReplaceInStory(ByVal targetRange As Range, ByVal findText As String, ByVal replaceText As String) As Long
Dim hitCount As Long
hitCount = 0
With targetRange.Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = findText
.Replacement.Text = replaceText
.Forward = True
.Wrap = wdFindStop
.Format = False
.MatchCase = True ‘ 大文字小文字を区別するか
.MatchWholeWord = False
.MatchWildcards = False ‘ ワイルドカードを使う場合はTrueに
‘ 一括置換を実行し、置換されたかどうかを判定
‘ Executeメソッドは置換が発生した場合にTrueを返す
If .Execute(Replace:=wdReplaceAll) Then
‘ WordのFind機能は一括置換の正確なヒット数を返さないため、
‘ 実務上は「置換が行われた」という事実をカウントする
hitCount = 1
End If
End With
ReplaceInStory = hitCount
End Function
—
コードのキリンテクト(重要解説)
1. `Application.ScreenUpdating = False` の徹底
セクションやヘッダーの数だけWordの画面描画が走ると、処理が極端に遅くなるだけでなく、画面が激しく点滅する。バックグラウンドで一瞬で処理を完結させるのがプロの作法だ。
2. `wdHeaderFooterFirstPage` などの網羅
「奇数/偶数ページ別指定」や「表紙(先頭ページ)のみ別指定」が有効になっているドキュメントでは、通常のプライマリヘッダー以外にアクセスしないと文字列を取りこぼす。配列を使って3種類すべてのパターンを確実に叩く設計にしている点がこのコードのキモである。
3. `Range.Find` のカプセル化
本文用、ヘッダー用、フッター用で同じ置換ロジックを何度も書くのはナンセンスだ。`ReplaceInStory` というプライベート関数に切り出すことで、コードの保守性と可読性を極限まで高めている。
—
データベース・外部ファイル連携時の注意点
このVBAマクロを、Excelからの制御や、業務システム(RPAやVB.NET等の外部アプリケーション)から呼び出すアーキテクチャに拡張する場合、以下の点に留意せよ。
- Wordプロセスの不可視化(Automation)
外部からWordを操作する場合、`wdApp.Visible = False` で実行することが多いが、ヘッダーやフッター内のストーリー操作は不可視状態でも問題なく動作する。ただし、エラー時にWordのプロセスがメモリ上に残り続ける「ゾンビプロセス」問題が発生しやすい。必ず `On Error` で終了時(あるいはExit時)に `wdApp.Quit` と変数の解放(`Set wdApp = Nothing`)を行うこと。
- 置換リストの外部化
ハードコードされた文字列ではなく、ExcelのマスタシートやCSVから置換前後のマッピング配列を動的に読み込み、ループの外側でこの `ReplaceInStory` 関数を回す設計にすれば、あらゆる文書フォーマット変換ツールへとスケールさせることができる。
総括
「動けばいいや」で作られたVBAは、文書の仕様が少し変わっただけで簡単に崩壊する。
今回解説したストーリーの概念とセクション走査のロジックをマスターすれば、どんなに複雑なレイアウトのWord文書が来ようとも、文字の取りこぼしを完全に防ぐことができる。
現場の信頼を勝ち取る「壊れない自動化ツール」を、あなたの手で実装してほしい。
