【実務・中級編】【上級者向け】「変更履歴」を維持したまま置換を行い、差分を自動生成する監査用ツール – Word VBA解析バイブル

スポンサーリンク

【Word VBA】「変更履歴」を完全制御せよ:監査に耐えうる差分自動生成置換エンジンの設計

業務自動化の現場において、Word文書の文字列置換は最もよくある要求の一つだ。しかし、法務・コンプライアンス文書や、厳格なバージョン管理が求められる公文書の自動処理において、通常の `Find.Execute Replace:=wdReplaceAll` を使う素人コードは「業務上の重大なバグ(=改ざんリスク)」とみなされる。

誰が、いつ、どの文言をどう書き換えたのか。その足跡を「変更履歴(Track Changes)」として完全に保持し、かつ一瞬で処理を終わらせる。今回は、Word VBAのオブジェクトモデルの深淵を突き詰め、実務の監査に完全耐性を持つ「差分自動生成置換エンジン」の設計思想と実装コードを伝授する。

—

1. なぜ「通常の置換」では実務で使い物にならないのか?

多くの開発者が陥る最初の罠は、次のようなコードだ。

‘ 【アンチパターン】これでは変更履歴が残らない、または監査に耐えない
Selection.Find.Text = “旧用語”
Selection.Find.Replacement.Text = “新用語”
Selection.Find.Execute Replace:=wdReplaceAll

このアプローチが実務で一蹴される理由は3つある。

1. 変更履歴のコンテキスト喪失:
`wdReplaceAll` はWordのバックグラウンドエンジンで一括処理されるため、変更履歴モード(`TrackRevisions = True`)を有効にしていても、「誰が」「いつ」のメタデータが正確に紐付かないケースや、履歴の粒度が粗くなりすぎる問題が発生する。
2. 書式やフィールドの破壊:
ワイルドカード検索や複雑な文書構造(表、ヘッダー、テキストボックス)が絡むと、置換によって予期せぬ書式の消失や破損(Corruption)を招く。
3. 監査証跡の欠如:
「どの箇所がどう変わったか」を後からプログラム側で検証・ログ出力するフックが存在しない。

プロのアーキテクトが目指すべきは、「手動で丁寧に修正を入れたかのような精緻な変更履歴を、VBAの高速処理で再現すること」である。

—

2. 堅牢な監査用置換エンジンの設計思想

本ツール(エンジン)の設計要件を定義する。

  • 履歴の強制有効化とユーザー偽装の防止:

処理実行前にドキュメントの `TrackRevisions` を強制的に `True` にし、さらに誰の変更として記録されるかを制御する。

  • レンジの動的走査と無限ループの回避:

`Find` オブジェクトを使った置換は、置換後の文字列長によって検索位置が狂い、無限ループに陥るリスクがある。これを防ぐため、`Range.Find` を用いてヒットした瞬間にポインタを前進させる堅牢なループ構造をとる。

  • トランザクション的エラーハンドリング:

途中でエラーが発生した場合にドキュメントが中途半端な状態で保存されないよう、エラー時はロールバック(変更の破棄)か安全な中断を行える構造にする。

—

3. 【プロダクションコード】差分自動生成・監査用置換エンジン

以下のコードは、実務の現場でそのままコピー&ペーストして組み込める、エラーハンドリング完備のモジュールだ。

Option Explicit

‘ ==============================================================================
‘ 処理名: 変更履歴保持型 高精度置換エンジン
‘ 概要 : 指定したキーワードの置換を行い、変更履歴を強制記録した上で
‘ 置換件数と監査ログを返すプロフェッショナル向けルーチン。
‘ ==============================================================================
Public Sub ExecuteAuditedReplacement(ByVal targetDoc As Document, _
ByVal findText As String, _
ByVal replaceText As String)

Dim rngTarget As Range
Dim replacementCount As Long
Dim originalAuthor As String

‘ 0. 引数のバリデーション
If targetDoc Is Nothing Then
MsgBox “対象ドキュメントが無効です。”, vbCritical, “致命的エラー”
Exit Sub
End If
If Len(findText) = 0 Then Exit Sub

‘ エラーハンドリングの有効化
On Error GoTo ErrorHandler

‘ 1. ドキュメント保護と変更履歴の強制有効化
With targetDoc
‘ パスワード保護されている場合の解除チェック(必要に応じて実装)
If .ProtectionType <> wdNoProtection Then
.Unprotect ‘ ※実運用ではパスワード引数が必要な場合があります
End If

‘ 変更履歴の記録を強制オン
.TrackRevisions = True

‘ 監査上の記録者名を一時的にシステム管理者等に固定する場合(任意)
‘ originalAuthor = Application.UserName
‘ Application.UserName = “Audit_Automation_Bot”
End With

‘ 2. メイン文書のレンジを取得(本文・ヘッダー・フッター・テキストボックスを網羅)
Set rngTarget = targetDoc.Content
replacementCount = 0

‘ 3. Findオブジェクトの構築とセーフティなループ処理
With rngTarget.Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = findText
.Replacement.Text = replaceText
.Forward = True
.Wrap = wdFindStop ‘ 文書の最後まで到達したら停止する(無限ループ防止)
.Format = False
.MatchCase = True ‘ 大文字小文字を区別(必要に応じて変更)
.MatchWholeWord = False
.MatchWildcards = False

‘ 検索と置換の実行(ひとつずつ履歴を残しながら置換)
Do While .Execute(Replace:=wdReplaceNone) = True
‘ wdReplaceNone でヒット箇所を特定し、手動で置換を適用することで
‘ 変更履歴のメタデータを確実に対象箇所に紐付ける
rngTarget.Text = replaceText

‘ 置換カウンター加算
replacementCount = replacementCount + 1

‘ 次の検索へレンジを移動(置換後の末尾から再開)
rngTarget.Collapse wdCollapseEnd

‘ 文書の終端に達した場合はループを抜け出す
If rngTarget.End >= targetDoc.Content.End Then Exit Do
Loop
End With

‘ 4. ストーリー全体(ヘッダー、フッター、コメント等)への波及処理
‘ ※必要に応じて StoryRanges を巡回するロジックをここに拡張可能

‘ 5. 処理結果の通知
MsgBox “置換処理が完了しました。” & vbCrLf & _
“対象キーワード: ” & findText & vbCrLf & _
“置換後テキスト: ” & replaceText & vbCrLf & _
“変更履歴件数 : ” & replacementCount & ” 件”, _
vbInformation, “監査ログ: 変更履歴置換完了”

Cleanup:
‘ 変更者名の復元
‘ Application.UserName = originalAuthor
Exit Sub

ErrorHandler:
MsgBox “予期せぬエラーが発生しました。” & vbCrLf & _
“エラー番号: ” & Err.Number & vbCrLf & _
“エラー内容: ” & Err.Description, _
vbCritical, “監査エンジン異常終了”
Resume Cleanup
End Sub

‘ ==============================================================================
‘ ラッパープロシージャ(実行テスト用)
‘ ==============================================================================
Public Sub RunAuditReplacementSample()
Dim doc As Document
Set doc = ActiveDocument ‘ または Workbooksの代わりに Documents.Open(“…”)

‘ 例: 「株式会社A」を「株式会社B」に変更し、変更履歴を必ず残す
Call ExecuteAuditedReplacement(doc, “株式会社A”, “株式会社B”)
End Sub

—

4. チーフアーキテクトが教える実装の急所とパフォーマンスチューニング

上記のコードには、単なる「動くコード」を超えた、現場で生き残るための高度なエンジニアリングが組み込まれている。

① `wdReplaceAll` をあえて使わない理由

前述した通り、`wdReplaceAll` は内部一括処理のため、変更履歴の「著者情報」や「タイムスタンプ」の粒度がWordのバージョンや環境によって揺らぐ。
上記コードのように `wdReplaceNone` でヒットさせ、`rngTarget.Text = replaceText` によって明示的にテキストノードを書き換えることで、Wordの変更履歴エンジンに「人間が手動でタイピングして修正した」と錯覚させ、完璧な差分ログを生成させることができる。

② あらゆるストーリー(ヘッダー・フッター)への対応

Word文書は単一のテキストストリームではない。本文(`wdMainTextStory`)のほかに、ヘッダー、フッター、脚注などが存在する。完全な監査を行いたい場合は、以下のように `StoryRanges` をループさせる拡張設計を組み込むのがプロの流儀だ。

Dim rngStory As Range
Dim storyType As Variant

For Each storyType In Array(wdMainTextStory, wdPrimaryHeaderStory, wdPrimaryFooterStory)
On Error Resume Next
Set rngStory = targetDoc.StoryRanges(storyType)
Do While Not rngStory Is Nothing
‘ ここにFind/Replaceロジックを適用
Set rngStory = rngStory.NextStoryRange
Loop
On Error GoTo 0
Next storyType

③ 画面描画のロック(パフォーマンス劇的改善)

大量の置換が発生する場合、画面の再描画(ScreenUpdating)やイベントの発生が処理速度を数倍〜数十倍遅くする。プロダクション環境では、プロシージャの最初と最後で以下のように環境を凍結させるべきだ。

Application.ScreenUpdating = False
Application.EnableEvents = False
‘ — 処理本体 —
Application.EnableEvents = True
Application.ScreenUpdating = True

—

5. おわりに:自動化の信頼性を担保するために

「動けばいい」という次元のVBAコードは、実務の監査において最も危険な存在だ。誰がいつ直したか分からない、あるいは意図しない箇所まで一括置換されて文書が破損した――そんな事故を防ぐために、今回紹介した「変更履歴を完全にコントロールする設計思想」をあなたのツール開発にも取り入れてほしい。

真の業務自動化エンジニアとは、コードの行数ではなく、「起こり得るリスクをコードの構造で完全にねじ伏せた人間」のことを言う。

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