【テクニカル・上級編】VBAでWordの『変更履歴』を制御する:校閲モードのオンオフと変更箇所をRangeで特定する方法 – Word VBA解析バイブル

スポンサーリンク

Word VBAを掌握する極限の知見:変更履歴の完全制御とRange最適化アーキテクチャ

Word VBAにおける「変更履歴(Track Revisions)」の制御は、多くの開発者が挫折する領域だ。画面上のカーソル操作とオブジェクトモデルの挙動が乖離しており、不適切なコードはメモリリーク、想定外の改変、そして数万行の文書処理における致命的なパフォーマンス低下を引き起こす。

本稿では、単なるメソッドの羅列ではなく、Wordのオブジェクトライフサイクル、メモリ管理、そして変更箇所(Revision)を`Range`オブジェクトとして正確に特定・操作するための実践的かつ極限まで洗練されたアーキテクチャを解説する。

1. 変更履歴エンジンの内部構造とライフサイクルの真実

Wordの`Document.TrackRevisions`プロパティを操作する際、背後ではCOMコンポーネントがドキュメントの差分バッファを構築・更新している。ここで重要なのは、「変更履歴のオン/オフ」と「既存の変更履歴の存在」は完全に直交しているという点だ。

シニアエンジニアが押おくべき原則は以下の通りである。

  • `TrackRevisions = True`にしても、すでに存在する未承諾の変更履歴が消えるわけではない。
  • 変更履歴は `Revisions` コレクションとしてドキュメント内に保持されるが、このコレクションのイテレーションは非常にコストが高い。
  • 安易な `For Each` ループは、WordのCOMレイヤーとVBA間のMarshalling(境界越えの通信)を多発させ、パフォーマンスを崩壊させる。

2. 校閲モードの強制制御と安全なスコープ管理

システム間連携や自動バッチ処理において、文書が意図せず変更履歴オフの状態で保存されるリスクを防ぐため、VBA側で強制的にガバナンスを効かせることが求められる。

以下のコードは、文書を開いた時点で変更履歴を強制的に有効化し、さらに特定の著者(Author)以外の変更を受け付けない、あるいは特定のスコープ内でのみ変更を許可する堅牢な初期化パターンである。

‘ ==============================================================================
‘ モジュール名: MdlRevisionController
‘ 概要: 変更履歴の強制制御と環境の安全な初期化を行うアーキテクチャ
‘ ==============================================================================
Option Explicit

Public Sub InitializeStrictRevisionMode(ByVal targetDoc As Document, ByVal forcedAuthor As String)
On Error GoTo ErrorHandler

‘ 画面描画と警告を抑制し、COMの更新コストを極限まで削減する
With Application
.ScreenUpdating = False
.DisplayAlerts = wdAlertsNone
End With

With targetDoc
‘ 変更履歴の強制有効化
.TrackRevisions = True

‘ 変更履歴の表示設定(必要に応じて非表示にも制御可能)
.ShowRevisions = True

‘ 監査ログの正確性を担保するため、作業者名(著者)を強制上書き
If Len(forcedAuthor) > 0 Then
.Application.UserName = forcedAuthor
.Application.UserInitials = Left$(forcedAuthor, 3)
End If

‘ 変更履歴のロック(パスワードによる保護の適用例)
‘ .Protect Type:=wdAllowOnlyRevisions, NoReset:=True, Password:=”SecurePass123″
End With

CleanUp:
‘ 確実に環境を復元
With Application
.ScreenUpdating = True
.DisplayAlerts = wdAlertsAll
End With
Exit Sub

ErrorHandler:
MsgBox “Critical Error in InitializeStrictRevisionMode: ” & Err.Description, vbCritical
Resume CleanUp
End Sub

3. `Range` を用いた変更箇所の高精度特定とメモリ最適化

多くのエンジニアブループは `For Each rev In doc.Revisions` を使い、変更箇所を走査する。しかし、文書の編集に伴って `Revisions` コレクションのインデックスや参照先が動的に変動するため、ループ内で削除や承諾を行うと致命的なインデックスズレや実行時エラーを引き起こす。

さらに、`Revision.Range` は毎回新しい COM オブジェクトをヒープ上に生成するため、ループのたびにメモリ消費が増大する。これを防ぐには、逆順ループ(Backward Loop)を採用し、取得した `Range` を適切に解放・管理する必要がある。

以下のコードは、特定のユーザーが行った変更箇所を `Range` として抽出し、特定の処理(例:ハイライトや抽出ログの作成)を行う極限まで最適化されたロジックである。

Public Sub ExtractAndProcessRevisions(ByVal targetDoc As Document, ByVal targetAuthor As String)
Dim revs As Revisions
Dim i As Long
Dim targetRange As Range

‘ オブジェクトの事前参照取得
Set revs = targetDoc.Revisions

‘ 逆方向ループによるコレクション変動の安全な処理
For i = revs.Count To 1 Step -1
‘ 毎回新しいオブジェクトが生成されるため、変数スコープで確実にキャプチャ
Dim currentRev As Revision
Set currentRev = revs(i)

‘ 指定した著者による変更、かつ「挿入」または「削除」に限定する場合のフィルタリング
If currentRev.Author = targetAuthor Then

‘ RevisionからRangeを取得
Set targetRange = currentRev.Range

‘ 【パフォーマンス最適化】Rangeに対する直接操作
Select Case currentRev.Type
Case wdRevisionInsert
‘ 例:挿入されたテキストに対する処理(背景色を黄色にする)
targetRange.HighlightColorIndex = wdYellow

Case wdRevisionDelete
‘ 例:削除されたテキストに対する処理(必要に応じたログ出力など)
‘ 削除されたテキストのRangeは文書上の特定の位置を示す
Debug.Print “Deleted by ” & targetAuthor & “: ” & targetRange.Text

Case Else
‘ 書式変更などのその他のタイプ
‘ 必要に応じた処理を記述
End Select

‘ 【メモリ管理】ループごとのオブジェクト解放
‘ VBAのガベージコレクションに頼らず、明示的にNothingを設定する
Set targetRange = Nothing
End If

Set currentRev = Nothing
Next i

‘ コレクション自体の参照もクリア
Set revs = Nothing

targetDoc.Save
MsgBox “変更箇所の特定と処理が完了しました。”, vbInformation
End Sub

4. レガシー環境・大規模文書におけるパフォーマンスの極意

数万行におよぶ仕様書や契約書において、変更履歴の「一括承諾(AcceptAll)」や「一括拒否(RejectAll)」は、Wordを数分間フリーズさせることがある。この原因は、UIスレッドとロジック処理が競合し、Undo(元に戻す)バッファが肥大化するためだ。

シニアエンジニアとして、このボトルネックを回避するためには以下のチューニングを施す必要がある。

1. Undoバッファの無効化(実質的ではないが、一括処理前の状態管理)
Word VBAには `Application.UndoRecord` を用いたトランザクション制御が存在する。不要な履歴の細分化を防ぎ、処理をアトミックにまとめる。
2. イベントの完全遮断
`Document_Change` や `WindowSelectionChange` などのイベントハンドラが実装されている場合、変更履歴を走査するたびにイベントが発火し、システムが深刻なデッドロックやパフォーマンス低下に陥る。必ず `Application.EnableEvents = False` を挟むこと。

Public Sub BatchAcceptRevisionsSafely(ByVal targetDoc As Document)
‘ イベントと画面描画の完全遮断
With Application
.ScreenUpdating = False
.EnableEvents = False
.DisplayAlerts = wdAlertsNone
End With

On Error GoTo ErrorHandler

‘ 文書全体の変更履歴を一括承諾(COMのネイティブメソッドを活用し、VBAループを回さない)
‘ ※条件付きで承諾したい場合は前述の逆順ループを使用するが、全承諾でよい場合はこれが最速
targetDoc.AcceptAllRevisionsShown

ErrorHandler:
If Err.Number <> 0 Then
MsgBox “Error occurred during batch acceptance: ” & Err.Description, vbCritical
End If

‘ 状態の復元
With Application
.ScreenUpdating = True
.EnableEvents = True
.DisplayAlerts = wdAlertsAll
End With
End Sub

5. 総括

Word VBAにおける変更履歴の制御は、単なる「プロパティの書き換え」ではない。COMオブジェクトのライフサイクルを理解し、メモリリークを防ぎ、巨大な文書バッファをいかに効率的にハンドリングするかという、システムアーキテクチャの縮図である。

安易なコード記述はシステムを不安定にするが、本稿で示したような適切なオブジェクト管理、逆順ループによる安全なコレクション走査、そしてイベント・描画の徹底的な抑制を行えば、企業レベルの大規模文書処理基盤としても十分に耐えうる堅牢なシステムを構築できる。

現場のプロフェッショナルとして、常にメモリとパフォーマンスに責任を持ったコードを書き上げてほしい。

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