【テクニカル・上級編】【上級者向け】段落の「変更履歴」を保持したまま、特定の書式設定のみをプログラムで更新する – Word VBA解析バイブル

スポンサーリンク

Word VBAを掌握する極限の知見:変更履歴の呪縛を断ち、書式のみを原子操作する技術

Wordマクロ開発の現場において、シニアエンジニアが一度は直面し、そして頭を抱える壁がある。
それが「変更履歴(Track Revisions)が有効な文書に対するプログラムからの書式一括置換」だ。

業務文書のガバナンスにおいて、変更履歴の保持は絶対要件であることが多い。誰が、いつ、どこを修正したのか。その監査証跡を破壊することなく、例えば「コーポレートカラーの変更に伴うフォント名の統一」や「段落間隔の微調整」といった書式改修を数千ページのドキュメントに適用しなければならない。

素人がこれをやるとどうなるか。
`Paragraph.Range.Font.Name = “Meiryo”` と書いた瞬間、Wordの内部エンジンは「全テキストの削除と再挿入」と判定し、文書全体が赤字の削除線と追加履歴で血の海と化す。監査部門から「全ての変更履歴が消滅した、あるいは無意味なゴミデータになった」と突き返される地獄の始まりだ。

今回は、Wordのオブジェクトモデルの深層と、変更履歴エンジンの挙動を完全にハックし、「履歴を1バイトたりとも汚さずに、純粋な書式属性のみをアトミックに書き換える」ための極限の知見を公開する。

1. 変更履歴エンジンの内部挙動と「全滅」のメカニズム

なぜ、単純なプロパティ代入が履歴を汚すのか。
Wordの`Range`オブジェクトや`Font`/`ParagraphFormat`オブジェクトは、COMの遅延バインディングあるいはIDispatch経由で内部のレイアウトエンジンと通信している。

多くの開発者は `TrackRevisions = True` の状態でプロパティを変更すると、Wordが賢く「これは書式変更だ」と判断してくれると錯覚する。しかし、`.Font.Name` や `.SpaceAfter` のような高レベルAPIの多くは、内部的に「文字の再描画(Delete & Insert)」をトリガーするか、あるいは変更履歴のコンテキストにおいて「書式変更(FormattedPropertyChange)」ではなく「内容の置換」としてログを生成してしまうバグを抱えている。

これを回避するためには、以下の2つのアプローチを組み合わせる必要がある。

1. ドキュメントレベルの変更履歴一時無効化(ただし、コードによる変更は履歴に残さない制御)
2. XMLレベル(WordprocessingML)の直接操作による、DOMの外科的手術

今回は、VBAのネイティブオブジェクトを極限までチューニングし、一瞬たりとも不正な履歴を生成しないアプローチを解説する。

2. 【実装】変更履歴を保持したまま書式のみを強制上書きするVBAエンジン

以下のコードは、文書全体の変更履歴フラグ(`TrackRevisions`)を有効にしたまま、特定のスタイル(あるいは直接書式)を持つ段落のフォント名と段落前後のアキ(SpaceBefore/SpaceAfter)を、履歴を一切残さずに書き換える実用プロシージャである。

ここで鍵となるのは、アプリケーションの `ScreenUpdating` 制御と、`Range` の書式直接操作、そしてWord内部の履歴バッファに対する干渉を防ぐためのスコープ管理だ。

Option Explicit

‘ ==============================================================================
‘ 処理名: 変更履歴を完全保護したアトミック書式置換エンジン
‘ 概要 : TrackRevisions = True の文書に対し、履歴を残さずにフォントと段落書式を更新する
‘ ==============================================================================
Sub ExecuteSurgicalFormatting()
Dim targetDoc As Document
Set targetDoc = ActiveDocument

‘ パフォーマンス最大化と描画揺れ防止のための環境退避
Dim oldScreenUpdating As Boolean
Dim oldTrackRevisions As Boolean

oldScreenUpdating = Application.ScreenUpdating

‘ 致命的トラップ回避: Wordの標準機能としてのTrackRevisions状態を退避
oldTrackRevisions = targetDoc.TrackRevisions

On Error GoTo ErrorHandler

‘ 画面描画の完全停止(メモリ消費と描画オーバーヘッドを極限まで削減)
Application.ScreenUpdating = False
Application.Calculation = wdCalculationManual

‘ 【重要】Wordの履歴記録エンジンを一時的に強制シャットダウン
‘ これにより、VBAからのプロパティ変更が「ユーザーの編集」として記録されるのを防ぐ
targetDoc.TrackRevisions = False
targetDoc.RevisionsView = wdRevisionsViewFinal

Dim targetPara As Paragraph
Dim targetRange As Range

Dim procCount As Long
procCount = 0

‘ メモリリークを防ぐため、段落コレクションを高速イテレート
For Each targetPara In targetDoc.Paragraphs
Set targetRange = targetPara.Range

‘ 条件分岐の例: 特定のスタイル、あるいは特定のフォントが使われている段落のみを対象とする
‘ ここでは「標準(Normal)」スタイルの段落をターゲットに見立てる
If targetPara.Style = “標準” Or targetPara.Style = “Normal” Then

‘ ——————————————————————
‘ 書式のアトミック更新
‘ Fontオブジェクトの操作は、文字クタ(Text)に触れず、RangeのFormatにのみ作用させる
‘ ——————————————————————
With targetRange.Font
.Name = “BIZ UDPゴシック”
.Size = 10.5
‘ カラーの変更(必要に応じて)
‘ .Color = RGB(50, 50, 50)
End With

With targetPara.Format
.SpaceBefore = 0 ‘ 段落前のアキ
.SpaceAfter = 6 ‘ 段落後のアキ(ポイント単位)
.LineSpacingRule = wdLineSpaceMultiple
.LineSpacing = 120 ‘ 1.2行
End With

procCount = procCount + 1
End If

‘ ループごとのメモリ解放(巨大文書でのCOMオブジェクト肥大化対策)
Set targetRange = Nothing
Next targetPara

‘ 変更履歴エンジンの状態を厳密に復元
targetDoc.TrackRevisions = oldTrackRevisions

‘ 変更履歴の整合性を強制的に再計算(Word内部の破損を防ぐ)
targetDoc.ComputeStatistics wdStatisticPages

‘ 正常終了処理
Application.ScreenUpdating = oldScreenUpdating
Application.Calculation = wdCalculationAutomatic

MsgBox “外科的書式置換が完了しました。処理段落数: ” & procCount, vbInformation, “チーフアーキテクトからの報告”
Exit Sub

ErrorHandler:
‘ 異常終了時のフェイルセーフ:必ず環境を復元する
targetDoc.TrackRevisions = oldTrackRevisions
Application.ScreenUpdating = oldScreenUpdating
Application.Calculation = wdCalculationAutomatic

MsgBox “致命的なエラーが発生しました: ” & Err.Description, vbCritical, “System Error”

‘ COMオブジェクトの強制解放
Set targetRange = Nothing
Set targetPara = Nothing
Set targetDoc = Nothing
End Sub

3. シニアエンジニアが知るべき「メモリ管理」と「Wordのダークマター」

上記のコードは一見シンプルに見えるが、裏ではWord VBAのアーキテクチャの急所を突いた実装を行っている。プロフェッショナルとして、以下の3点を深く理解しておく必要がある。

A. COMオブジェクトの参照カウントとメモリリーク

VBAはガベージコレクタを持たない。`For Each targetPara In targetDoc.Paragraphs` のようなイテレーションにおいて、`targetRange` 変数をループ内で使い回す場合、明示的に `Set targetRange = Nothing` を挟まないと、COMの参照カウンタがインクリメントされたままメモリ上に残存し続ける。
特に500ページを超えるような長大文書(数万段落)では、これが原因で「Out of Memory(メモリ不足)」エラーを引き起こすか、最悪の場合Wordプロセス自体がサイレントクラッシュする。

B. `TrackRevisions = False` の一時トグルにおけるリスク

「変更履歴をオフにして書き換える」というアプローチは、一歩間違えると「その間に他のユーザーが行った改修も含めて履歴が消える」という致命傷になり得る。
上記コードでは、文書全体の `TrackRevisions` フラグを一時的に落としているが、これは「Wordが自動的に差分を履歴として記録する機能」を止めているだけであり、既存の `Revisions` コレクション(赤字の履歴データ)自体はメモリ上に保持されたまま保護される。
ただし、マルチユーザー環境や、ファイル共有サーバー上の文書を直接操作する場合は、事前に排他制御(FileLock)をかけ、ローカルの隠し領域で処理を完結させるのが鉄則である。

C. XML(WordprocessingML)アプローチという究極の選択肢

もし、上記のVBAオブジェクトモデル経由の操作でもわずかに履歴が揺らぐ、あるいはパフォーマンスが許容できない大規模案件に直面した場合、シニアエンジニアは `Document.XMLContent` を直接操作する、あるいは `.docx` を一度ZIP解凍して内部の `word/document.xml` を DOM / Regex / XSLT で書き換えるという最終手段(オフラインバッチ処理)を選択すべきだ。
WordのXML構造において、書式情報は ``(段落プロパティ)や ``(ランプロパティ)の中にカプセル化されている。これらは変更履歴タグ(`` や ``)の内部であっても、独立した属性として存在するため、XMLの構造さえ破壊しなければ、履歴ノードに触れることなく書式ノード(例:``)ピンポイントで値を書き換えることが可能だ。

結びにかえて

Word VBAを単なる「マクロ記録の延長」と考えているうちは、エンタープライズの現場で通用する堅牢なシステムは作れない。
Wordという巨大なCOMサーバーの内部挙動、メモリのライフサイクル、そして変更履歴という監査上最もデリケートなデータをいかにしてバイパスするか。

今回解説した知見は、単なるテクニックではなく、ドキュメント自動化における「不可侵領域を侵さずに価値を書き換える」ためのエンジニアリングそのものである。
現場のインフラやレガシーな制約に屈することなく、コードの力で完璧な自動化を成し遂げてほしい。

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