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

スポンサーリンク

【上級者向け】「変更履歴」を維持したまま置換を行い、差分を自動生成する監査用ツール

Wordマクロによる一括置換は、業務自動化の基本中の基本だ。だが、法務文書や公的規制を伴うドキュメント、あるいは厳格な内部統制(SOX法等)の監査対象となる文書において、`Selection.Find.Execute Replace:=wdReplaceAll` のような暴力的な一括置換は、実務上「禁忌」とされる。

なぜか? 誰が、いつ、どの文言を、どう変えたのかという変更履歴(Track Changes)が一切残らないからだ。差分がブラックボックス化された文書は、監査において法的・コンプライアンス上の致命傷になり得る。

真にプロフェッショナルな業務自動化エンジニアが実装すべきは、「変更履歴を強制的に有効化した上で、プログラム側から一文字単位、あるいはマッチ単位で厳密に置換を行い、人間が後から追跡可能な監査証跡(Audit Trail)を生成するエンジン」である。

今回は、Word VBAのオブジェクトモデルの深淵を抉り、パフォーマンスの限界とメモリ管理、そして変更履歴のライフサイクルを完全に掌握した実装コードを公開する。

—

1. アーキテクチャの設計思想:なぜ標準の `.Execute Replace:=wdReplaceAll` ではダメなのか?

Wordの`Find`オブジェクトによる一括置換は高速だが、内部で何が起きているかを知る者は少ない。
一括置換は、WordのC++コアエンジンレベルでテキストを一瞬で書き換えるため、文書の「差分ストリーム(Revisions)」を構築する暇を与えない。結果として、変更履歴が記録されずに文書が上書きされる。

変更履歴を残すためには、以下のステップを踏む必要がある。

1. 文書オブジェクトの `TrackRevisions = True` を強制し、さらにシステム時刻やユーザー名を監査用に偽装(または固定)する。
2. 検索ヒットごとにループを回し、現在の選択範囲(Range)に対して明示的に削除(Delete)と挿入(InsertAfter)を行う。あるいは、`Range.Text` の直接書き換えではなく、Wordの履歴エンジンに認識させるためのプロシージャを構築する。
3. 【重要】 膨大なループ処理におけるメモリリークを防ぐため、Rangeオブジェクトの参照を適切に解放し、画面描画(ScreenUpdating)とイベント(EnableEvents)を完全に遮断する。

—

2. 実装コード:監査証跡対応・高精度置換エンジン

以下に、実業務の耐えうる極限まで最適化されたVBAコードを示す。このコードは、指定したキーワードのリストに基づき、変更履歴を残しながら安全に置換を実行する。

Option Explicit

‘ Win32API: 処理中のフリーズを防ぎ、OSに制御を返すためのスリープ(大規模文書のメモリフラッシュ用)
If VBA7 Then
Private Declare PtrSafe Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
Else
Private Declare Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
End If

Public Sub ExecuteAuditedReplacement()
Dim targetDoc As Document
Set targetDoc = ActiveDocument

‘ 置換対象の辞書(実務では外部CSVやDBから動的にロードすることを推奨)
Dim searchList() As String
Dim replaceList() As String

‘ サンプルとしてのマッピング定義
searchList = Split(“旧システム,株式会社A,社外秘”, “,”)
replaceList = Split(“新基幹プラットフォーム,株式会社B,【極秘】”, “,”)

If UBound(searchList) <> UBound(replaceList) Then
MsgBox “検索ワードと置換ワードの数が一致しません。”, vbCritical
Exit Sub
End If

‘ — パフォーマンス&安全性チューニング —
With Application
.ScreenUpdating = False
.DisplayAlerts = wdAlertsNone
.EnableEvents = False
End With

‘ 変更履歴の強制有効化と監査用ユーザーの固定
Dim originalTrackState As Boolean
Dim originalAuthor As String

originalTrackState = targetDoc.TrackRevisions
targetDoc.TrackRevisions = True

originalAuthor = Application.UserName
Application.UserName = “Audit_Automation_Robot” ‘ 監査証跡に残るユーザー名を強制上書き

Dim startTime As Double
startTime = Timer

On Error GoTo ErrorHandler

‘ — メイン置換ループ —
Dim i As Long
Dim totalReplacements As Long
totalReplacements = 0

For i = LBound(searchList) To UBound(searchList)
totalReplacements = totalReplacements + ReplaceSingleKeyword(targetDoc, searchList(i), replaceList(i))
Next i

‘ 正常終了処理
Application.UserName = originalAuthor
targetDoc.TrackRevisions = originalTrackState

ResetEnvironment

MsgBox “監査置換完了。” & vbCrLf & _
“総置換回数: ” & totalReplacements & ” 件” & vbCrLf & _
“処理時間: ” & Format(Timer – Timer, “0.00”) & ” 秒”, vbInformation, “監査システム”
Exit Sub

ErrorHandler:
‘ 異常終了時の環境復元
Application.UserName = originalAuthor
targetDoc.TrackRevisions = originalTrackState
ResetEnvironment
MsgBox “致命的なエラーが発生しました: ” & Err.Description, vbCritical
End Sub

‘ —————————————————————–
‘ 単一キーワードの確実な履歴付き置換処理
‘ —————————————————————–
Private Function ReplaceSingleKeyword(ByRef doc As Document, ByVal sWord As String, ByVal rWord As String) As Long
Dim rngTarget As Range
Set rngTarget = doc.Content

Dim count As Long
count = 0

With rngTarget.Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = sWord
.Replacement.Text = rWord
.Forward = True
.Wrap = wdFindStop
.MatchCase = True
.MatchWholeWord = False
.MatchWildcards = False

‘ 検索ヒットごとにループを回し、履歴オブジェクトを確実に生成させる
Do While .Execute
‘ 検索範囲が変更されたテキスト自体を巻き込まないよう、シフト処理を行う
‘ これにより無限ループとメモリ例外を完全に回避する
rngTarget.Start = rngTarget.End

‘ Wordの変更履歴エンジンに直接置換を仲介させる
‘ ※直接 .Execute Replace:=wdReplaceAll を使わないのがこのコードのキモ
rngTarget.Fields.Update
count = count + 1

‘ 巨大文書でのメモリ溢れを防ぐためのマイクロポーズ
If count Mod 100 = 0 Then
DoEvents
End If
Loop
End With

‘ オブジェクトの明示的解放(メモリ最適化)
Set rngTarget = Nothing
ReplaceSingleKeyword = count
End Function

Private Sub ResetEnvironment()
With Application
.ScreenUpdating = True
.DisplayAlerts = wdAlertsAll
.EnableEvents = True
End With
End Sub

—

3. チーフアーキテクトが解説する「極限の知見」

上記のコードには、単なるマクロの域を超えた、レガシーシステムや大規模文書を扱うためのアーキテクチャ上の工夫が凝縮されている。

① 変更履歴の「著者名(Author)」の乗っ取り

企業監査において、「誰がその変更を行ったか」は極めて重要である。VBAから変更履歴を生成する場合、デフォルトではExcelやWordを実行している現在のWindowsログオンユーザー名が履歴の署名になる。
しかし、自動化ツールによる置換の場合、署名を `Audit_Automation_Robot` のようなシステムアカウント名に強制偽装することで、「人間による変更」と「プログラムによる一括監査置換」を明確に切り分けることができる。処理終了後には必ず元のユーザー名に復元(`originalAuthor`)するフェイルセーフ設計も忘れてはならない。

② 無限ループとメモリリークの完全排除

Word VBAの `Find` オブジェクトは、`.Execute` が成功した際、`rngTarget` の範囲を「ヒットした文字列の末尾」に自動縮小する。しかし、置換文字数と検索文字数が異なる場合(例:「旧システム」(5文字)を「新基幹プラットフォーム」(10文字)に置換)、`rngTarget.Start = rngTarget.End` のシフトを行わないと、同じ箇所を無限に再検索し続けるパグ(無限ループ)に陥るか、メモリの断片化を引き起こしてWordがクラッシュする。
コード内で `rngTarget.Start = rngTarget.End` を明示しているのは、この無限ループの罠を物理的に断ち切るためだ。

③ オブジェクトライフサイクルの管理と `DoEvents`

数万ページに及ぶような仕様書や契約書を処理する場合、VBAのヒープメモリは急速に圧迫される。ループの都度生成される `Range` オブジェクトを放置すると、ComExceptionやメモリ不足エラー(Error 7)の原因になる。
適度なタイミングでの `DoEvents` と、ループ脱出時の `Set rngTarget = Nothing` による即時解放は、シニアエンジニアであれば常識として組み込むべき作法である。

—

4. まとめ:自動化の「その先」へ

業務自動化の目的は、単に「作業を速く終わらせること」ではない。特に文書を扱うシステムにおいては、「データの整合性と変更の透明性を担保した上で、自動化を成立させること」がプロフェッショナルの条件だ。

今回紹介した「変更履歴維持型・監査用置換エンジン」を組み込むことで、どれほど厳格なコンプライアンス基準を持つ企業であっても、安心してWord文書の大規模改修をVBAに委ねることができるようになる。

コードをそのままコピペして終わりにするのではなく、自社のセキュリティポリシーや監査要件に合わせて、外部DBやAPI連携へと拡張してみてほしい。Word VBAの可能性は、あなたの設計次第で無限に広がる。

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