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

スポンサーリンク

Word VBAを掌握する極限の知見:変更履歴の完全制御とRangeによる領域特定

業務自動化エンジニアの皆さん、こんにちは。
Word VBAにおける最大の「地雷」の一つをご存知でしょうか?それは「変更履歴(Track Changes)」の制御です。

「`.TrackRevisions = True` にして文書を保存するだけ簡単だ」と思っていませんか?
もし実務で数万行の契約書やマニュアルを相手にするなら、その甘い認識は必ずシステム障害やデータ破損という形でしっぺがえしを受けます。Wordの変更履歴は、UI上の挙動とオブジェクトモデルの挙動が乖離しているケースが多く、適当なコードを書くと「意図しないユーザー名で記録される」「Accept/Rejectの判定漏れでゴーストテキストが残る」「Rangeのズレによる無限ループ」といった致命的なバグを引き起こします。

今回は、エンタープライズ環境のドキュメント処理に耐えうる、極限まで堅牢な変更履歴の制御ロジックと、`Range`オブジェクトを用いた変更箇所の精密な特定・抽出手法を伝授します。

1. 変更履歴制御のアーキテクチャと「3つの罠」

まず、Wordの変更履歴機能(`Revisions`コレクション)の裏側の挙動を理解してください。

1. アプリケーションスコープの汚染
`Application.UserName` や `Application.UserInitials` は、Word全体の設定です。VBAから一時的に校閲者名偽って変更履歴を残したい場合、このグローバル状態を書き換える必要がありますが、処理失敗時に元のユーザーに戻らない(ロールバックされない)事故が多発します。
2. `Range` と `Revisions` の非同期性
文書内の「変更箇所」を走査する際、`For Each` で `Revisions` を回しながらドキュメント構造を変更すると、インデックスがズレてパニックを起こします。
3. 「表示」と「データ」の混同
画面上で「変更履歴を表示しない(オリジナル)」にしている状態と、コードで「変更履歴がオフ」な状態は全く別物です。これを混同すると、意図せぬ履歴が埋め込まれたままクライアントに納品されることになります。

これらを完全にコントロールするため、「状態の退避・強制適用・確実な復元」を担保したプロダクションコードの設計を見ていきましょう。

2. 実務仕様:特定ユーザーの変更抽出と一括処理エンジン

以下のコードは、実務の現場で即座に使える堅牢なモジュールです。
特定の校閲者(例:「法務部A」)の変更箇所だけを `Range` として抽出し、別文書へ集約、あるいは一括処理するためのテンプレートになります。

Option Explicit

‘ ==============================================================================
‘ 処理名 : ExtractAndProcessRevisions
‘ 概要 : 指定したユーザーの変更履歴をRangeで特定し、別ドキュメントに抽出・処理する
‘ ==============================================================================
Public Sub ExtractAndProcessRevisions()
Dim targetDoc As Document
Dim outputDoc As Document
Dim rev As Revision
Dim targetUser As String
Dim extractedRange As Range

‘ ターゲットとする校閲者の名前(環境に合わせて変更)
targetUser = “法務部 太郎”

‘ 処理対象ドキュメント(アクティブ文書を想定)
Set targetDoc = ActiveDocument

‘ 変更履歴の保護(万が一オフならオンにするが、今回は監査目的なので既存を維持)
If Not targetDoc.TrackRevisions Then
MsgBox “この文書では変更履歴が記録されていません。”, vbExclamation, “処理中断”
Exit Sub
End If

‘ 出力用新規ドキュメントの作成
Set outputDoc = Documents.Add
outputDoc.Content.InsertAfter “【変更履歴抽出レポート】” & vbCrLf & _
“対象ユーザー: ” & targetUser & vbCrLf & _
“抽出日時: ” & Now & vbCrLf & vbString(50, “-“) & vbCrLf & vbCrLf

‘ エラーハンドリングの要:トランザクション的思考
On Error GoTo ErrorHandler

‘ 画面描画を停止して爆速化
Application.ScreenUpdating = False
Application.Calculation = wdCalculationManual

Dim count As Long
count = 0

‘ Revisionsコレクションを逆順(後ろから)走査するのが極意
‘ ※順方向で回すと、Range操作や削除時にインデックスが狂うため
Dim i As Long
For i = targetDoc.Revisions.Count To 1 Step -1
Set rev = targetDoc.Revisions(i)

‘ 指定したユーザーの変更かチェック
If rev.Author = targetUser Then
‘ 変更箇所のRangeを取得
Set extractedRange = rev.Range

‘ 【極限の知見】
‘ 変更内容の種類(挿入、削除、書式変更など)に応じたハンドリング
Select Case rev.Type
Case wdRevisionInsert
outputDoc.Content.InsertAfter “[追加] ” & extractedRange.Text & vbCrLf
count = count + 1

Case wdRevisionDelete
outputDoc.Content.InsertAfter “[削除] ” & extractedRange.Text & vbCrLf
count = count + 1

Case wdRevisionProperty
outputDoc.Content.InsertAfter “[書式変更] ” & extractedRange.Text & vbCrLf
count = count + 1

‘ 必要に応じて他の wdRevisionType を追加
End Select
End If
Next i

outputDoc.Content.InsertAfter vbCrLf & “合計検出数: ” & count & ” 件”

MsgBox “抽出が完了しました。検出件数: ” & count & “件”, vbInformation, “完了”

CleanUp:
‘ 状態の確実な復元
Application.ScreenUpdating = True
Application.Calculation = wdCalculationAutomatic
Exit Sub

ErrorHandler:
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “エラー”
Resume CleanUp
End Sub

3. コードの急所:なぜこの書き方が「プロフェッショナル」なのか?

上記のコードには、単なる「動くコード」を超えた、現場の修羅場をくぐり抜けてきたエンジニアの知見が詰まっています。

① `Revisions` コレクションを「後ろから(Step -1)」回す理由

初級者がやりがちなミスが `For Each rev in targetDoc.Revisions` です。このループ内で変更箇所の承諾(`Accept`)や削除を行ったり、文書構造に手を加えると、Wordの内部ポインタが破損し、実行時エラー 4605 や最悪の場合のワードファイルクラッシュを引き起こします。
後ろからインデックスで回すことで、メモリ上のインデックスズレを完全に回避できます。

② `Application.ScreenUpdating` と `Calculation` の封鎖

Word VBAの処理速度が劇的に遅い原因の8割は「画面描画」と「フィールド・数式の自動再計算」です。変更履歴の走査は数千箇所に及ぶことも珍しくありません。この数行のサンドイッチがあるかないかで、処理時間が「数分」から「数秒」に変わります。

③ トランザクション的なエラーハンドリング

処理途中でエラー落ちした際、画面描画が止まったまま(`ScreenUpdating = False` のまま)になり、ユーザーがフリーズしたと勘違いして強制終了する事故を防止するため、`CleanUp` ラベルを用いた確実な状態復元を義務化しています。

4. 発展:変更履歴の「強制一括承認・拒否」とデータベース連携への布石

例えば、「社内レビューが完了したため、全ての変更履歴を強制的に承認(Accept All)し、クリーンな納品用文書を作る」という自動化要求は非常に多いです。
これを手動や安易なコードで行うと、コメントや未解決のトラッキングが残ります。以下のスニペットは、それを極限まで安全に行うプロシージャです。

Public Sub ForceAcceptAllRevisions(ByVal targetDoc As Document)
‘ 変更履歴が存在するか確認
If targetDoc.Revisions.Count > 0 Then
‘ 全ての変更を一括承認
targetDoc.Revisions.AcceptAll
End If

‘ 念のためコメント(Comments)もすべて削除する場合
Dim c As Comment
For c = targetDoc.Comments.Count To 1 Step -1
c.Delete
Next c

‘ 変更履歴の記録自体をオフにする
targetDoc.TrackRevisions = False

‘ 変更を保存して閉じる等の処理へ続く…
End Sub

データベース・外部システム連携時の注意点

この変更履歴から抽出したテキストやメタデータ(誰が、いつ、どこを直したか)を、SQL ServerやExcel、あるいはWeb API経由でKintone等のDBに飛ばす設計にする場合、「Wordのタイムスタンプ(`rev.Date`)がUTCではなくPCのローカル時間で返る問題」「長文テキストによるString型のバッファあふれ」に注意してください。
必ず `Range.Text` を取得する際は、段落記号(`vbCr`)の混入を考慮し、必要に応じて `Trim` や置換処理(`Replace(text, vbCr, “”)`)を挟むのが、バックエンド連携におけるエンジニアの作法です。

総括

Word VBAにおける変更履歴制御は、単なるプロパティのオンオフではありません。
「オブジェクトのライフサイクルを理解し、メモリ上のインデックス破綻を防ぎ、エラー時すらも環境をクリーンに保つ」という、極めてアーキテクチャ指向の強い領域です。

今回紹介した設計パターンをあなたのプロジェクトに組み込めば、気まぐれなWordの挙動に悩まされることは二度となくなるはずです。現場の信頼を勝ち取る、強靭な自動化ツールを構築してください。

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