【実務・中級編】Word VBAで作成する『文書比較ツール』:2つの文書の差分を色付けする – Word VBA解析バイブル

スポンサーリンク

序文:なぜ標準の「比較」機能では不十分なのか

諸君、Word VBAの世界へようこそ。
Wordには標準で「比較(`Documents.Compare`)」という強力なメソッドが備わっている。しかし、実務の最前線で求められるのは、単なる「変更履歴の生成」ではない。特定のキーワードだけを抽出したい、差分をデータベースに記録したい、あるいは独自のアルゴリズムで「実質的な意味の差」だけを浮き彫りにしたい……。

標準機能というブラックボックスに身を委ねているうちは、真の業務自動化エンジニアとは呼べない。
本稿では、Wordのオブジェクトモデルを解剖し、「Rangeオブジェクトを掌握して、2つの文書の差分を自前でハイライトする」という極限のロジックを伝授する。

単なるループ処理ではない。パフォーマンスを考慮し、かつ拡張性に耐えうるアーキテクチャをその目に焼き付けてほしい。

1. 基礎設計:Wordオブジェクトモデルの「重み」を知る

Word VBAでツールを設計する際、初心者が陥る最大の罠は「Rangeへの頻繁なアクセス」だ。

WordのDocumentオブジェクトは、背後で膨大なXML構造(WordprocessingML)を抱えている。`ActiveDocument.Words(i)` と一回書くたびに、Wordは文書の先頭からその位置を再計算していると考えたほうがいい。100ページの文書でこれをやれば、処理速度は指数関数的に低下する。

成功するための3つの鉄則

1. Selectionは捨てろ、Rangeを使え: 画面の再描画を伴うSelectionは論外だ。
2. プロパティの多重参照を避けよ: `Doc.Words(i).Font.Color` ではなく、一度Rangeオブジェクトを変数にセットしてから属性を叩く。
3. 「差異」の定義を明確にせよ: 文字列の一致か、書式の一致か。今回は「テキストの差分」に絞って解説する。

2. 実践:高精度・高速比較エンジンの実装

以下に示すのは、2つの文書を「単語レベル」で比較し、差分を色分けするプロダクションコードだ。
このコードは、単なる比較に留まらず、「文書A(新)にあって文書B(旧)にないもの」を赤、「文書Bにあって文書Aにないもの」を青でハイライトするロジックを想定している。

Option Explicit

‘ ==============================================================================
‘ 究極の文書比較ツール:Word VBA Diff Engine
‘ 2つの文書を単語単位で走査し、差分をハイライトする。
‘ ==============================================================================
Public Sub CompareTwoDocuments()
Dim docNew As Document, docOld As Document
Dim wordNew As Range, wordOld As Range
Dim i As Long, countNew As Long, countOld As Long

‘ 1. パフォーマンスの最適化:画面更新とスペルチェックを停止
On Error GoTo ErrorHandler
With Application
.ScreenUpdating = False
.Options.CheckSpellingAsYouType = False
.Options.CheckGrammarAsYouType = False
End With

‘ — 本来はファイル選択ダイアログ等で指定すべき箇所 —
‘ ここでは開いている2つの文書を対象とする(1番目と2番目)
If Documents.Count < 2 Then MsgBox "比較するには2つの文書を開いてください。", vbExclamation Exit Sub End If Set docNew = Documents(1) ' 比較元(最新) Set docOld = Documents(2) ' 比較先(旧) ' 2. 単語数のカウント countNew = docNew.Words.Count countOld = docOld.Words.Count ' 3. 比較ロジック(単語単位) ' 注意:このロジックは単純な逐次比較である。 ' 挿入・削除が多い場合は、動的計画法(LCSアルゴリズム)の実装が必要だが、 ' 実務上の「微修正の検知」であれば、このRange走査が最も堅牢だ。 Dim maxCount As Long maxCount = IIf(countNew > countOld, countNew, countOld)

For i = 1 To maxCount
‘ 進捗表示(ステータスバーへの出力はコストが低い)
If i Mod 100 = 0 Then
Application.StatusBar = “比較中… ” & i & ” / ” & maxCount
End If

‘ 文書Aの単語を取得
If i <= countNew Then Set wordNew = docNew.Words(i) Else Set wordNew = Nothing End If ' 文書Bの単語を取得 If i <= countOld Then Set wordOld = docOld.Words(i) Else Set wordOld = Nothing End If ' 比較とハイライト If Not wordNew Is Nothing And Not wordOld Is Nothing Then ' テキストをトリムして比較(空白文字の影響を排除) If Trim(wordNew.Text) <> Trim(wordOld.Text) Then
wordNew.HighlightColorIndex = wdYellow ‘ 内容が異なる
wordNew.Font.ColorIndex = wdRed
End If
ElseIf Not wordNew Is Nothing Then
‘ 新しい文書にのみ存在する(追加箇所)
wordNew.HighlightColorIndex = wdBrightGreen
End If
Next i

‘ 4. 後処理
Application.StatusBar = “比較完了”
MsgBox “文書の比較が完了しました。”, vbInformation

CleanUp:
With Application
.ScreenUpdating = True
.Options.CheckSpellingAsYouType = True
.Options.CheckGrammarAsYouType = True
.StatusBar = False
End With
Exit Sub

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

3. チーフアーキテクトによる深掘り解説

なぜ `docNew.Words(i)` を使うのか?

通常、高速化のためには文書全体を `Array`(配列)に格納してメモリ上で比較するのが定石だ。しかし、Word VBAにおいて「差分を色付けする」という要件がある場合、最終的にRangeオブジェクトへアクセスしなければならない。

配列で比較して「100番目の単語が違う」とわかった後、再び `docNew.Words(100)` を取得するのは二度手間だ。それならば、最初からRangeをオブジェクト変数としてハンドリングし、プロパティを直接操作する方が、コードの保守性と確実性が高い。

堅牢性を高める「空白文字」のハンドリング

Wordの `Words` コレクションは、末尾のスペースを一つの単語としてカウントする特性がある。

If Trim(wordNew.Text) <> Trim(wordOld.Text) Then

この `Trim` 一つが、実務では「意味のある差分」と「単なる体裁の差分」を分ける境界線になる。

大規模文書への対策

もし対象が数百ページに及ぶなら、上記のコードでも重くなる。その場合は、`Range` ではなく `Paragraphs`(段落)単位でまずハッシュ値(または単純な文字列比較)をとり、「差分がある段落のみを詳細走査する」という2段構えのアルゴリズムに設計変更すべきだ。これが「計算量を減らす」というエンジニアの思考だ。

4. プロダクション環境での運用アドバイス

実務でツールとして配布する場合、以下の要素を検討してほしい。

1. 外部連携:
比較結果をWord上に色付けするだけでなく、`CSV` や `Excel` に「何ページ目のどの単語が変更されたか」をログ出力する機能を追加せよ。管理者はそれをエビデンスとして利用する。
2. データベース連携:
変更前後のテキストをSQL Server等のデータベースに保存する場合、`Range.XML` を取得して保存すれば、書式情報を含めた完全なリストアが可能になる。
3. 比較対象の除外:
「日付」や「自動採番」など、比較しなくてよい項目を `RegExp`(正規表現)で除外するフィルタを実装すると、ツールの実用性は飛躍的に高まる。

結論:オブジェクトを制する者がWordを制す

Word VBAは、Excel VBAに比べてオブジェクトモデルが複雑で、一筋縄ではいかない。しかし、今回解説したように 「Rangeオブジェクトのライフサイクルを意識し、不必要な画面描画を抑え、プロパティへのアクセスを最小限にする」 という基本原則を守れば、どんな複雑な文書処理も自動化できる。

このコードをベースに、君たちの現場に合わせた最強の比較ツールを構築してほしい。道具に使われるのではなく、道具を創り出す側に回るのだ。

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