【Word VBA極限の知見】数万ページを秒殺せよ:Word文書のXML直接DOM操作による超高速一括置換
Word VBAにおける最大のボトルネック、それは「オブジェクトモデルの往復」だ。
`.Find.Execute` をループさせたり、文書全体を走査したりするアプローチは、数千ページを超える長大なマニュアルや仕様書の前では完全に無力と化す。進捗バーは止まり、最悪の場合はCOM例外やメモリリークでExcel/Wordごと沈没する。
開発プロジェクトのリーダーである私たちが直面する現場の要求は冷酷だ。「正確に、かつ一瞬で終わらせろ」。
この絶望的なパフォーマンス限界を突破する唯一にして究極の解が、「Word文書をXML(FlatOPC形式)として解剖し、DOM(Document Object Model)で一刀両断した後に再構築する」 という実験的アプローチだ。
今回は、Wordの常識を覆すプロフェッショナル向けの実装手法を授けよう。
—
1. なぜオブジェクトモデルの置換は遅いのか?
日常的なVBAコードで書かれる以下の処理を見てほしい。
‘ 【アンチパターン】絶対にやってはいけない愚行
Selection.Find.ClearFormatting
Selection.Find.Text = “旧用語”
Selection.Find.Replacement.Text = “新用語”
Selection.Find.Execute Replace:=wdReplaceAll
一見シンプルだが、内部で何が起きているか?
WordのGUIエンジンとCOMインターフェースが会話を繰り返し、文書レイアウト(ページネーション)の再計算をヒットするたびに走らせている。これが数万ページの文書であれば、CPUはレイアウト計算にリソースを奪われ、I/Oは悲鳴を上げる。
思想の転換:Wordは「ただのZIPアーカイブ付きXML」である
`.docx` の実体は、OpenXML規格に準拠したZIPファイルである。中身を解凍すれば、テキストやスタイルはすべて純粋なXML(`word/document.xml` など)で記述されている。
ならば、Wordを起動して画面を描画させる必要すらない。
文書をXMLストリームとしてメモリ上に読み込み、DOM(Document Object Model)またはXPathを用いて文字列をごっそり置換し、書き戻す。これこそが、数万ページを一瞬で処理するための唯一のアーキテクチャである。
—
2. アーキテクチャ設計:FlatOPC(Word 2003 XML)の採用
通常、`.docx` はZIP構造(Package)を解凍・再圧縮する手間がかかり、VBA単体で処理するにはADODBやShell.Applicationを噛ませる必要がありコードが汚染される。
そこで本手法では、Wordのネイティブ形式である 「Word 2003 XML(FlatOPC)」 を利用する。
これは、複数のXMLパーツを1つの巨大なXMLファイルにフラットに結合した形式であり、VBA標準の `MSXML2.DOMDocument` で直接DOM操作が可能になるという、極めて高い親和性を持つ。
全体ワークフロー
1. 対象の `.docx` を一度Wordのバックグラウンド(非表示)で開き、FlatOPC形式(`wdFormatXMLDocument`)で別名保存する。
2. 生成されたXMLファイルを `MSXML2.DOMDocument.6.0` でロードする。
3. DOMのノード走査、またはXPathを用いて、テキストノードを高速置換する。
4. XMLファイルを上書き保存し、必要に応じて通常の `.docx` にコンバートする。
—
3. 【プロダクションコード】XML DOM置換エンジン
実務でそのまま組み込める、堅牢かつ洗練されたVBAコードを提供する。エラーハンドリング、名前空間の解決、メモリ解放まで完璧に網羅したプロのコードだ。
Option Explicit
‘ =========================================================================
‘ 開発現場向けプロダクションコード
‘ 依存関係: Microsoft XML, v6.0 (MSXML2.DOMDocument.6.0)
‘ =========================================================================
Public Sub ExecuteHighSpeedXmlReplace(ByVal targetFilePath As String, ByVal targetStr As String, ByVal replaceStr As String)
Dim fso As Object
Dim xmlDoc As Object
Dim tempXmlPath As String
Dim startTime As Double
startTime = Timer
Set fso = CreateObject(“Scripting.FileSystemObject”)
If Not fso.FileExists(targetFilePath) Then
MsgBox “指定されたファイルが存在しません: ” & targetFilePath, vbCritical
Exit Sub
End If
‘ 一時XMLファイルのパス生成
tempXmlPath = fso.GetParentFolderName(targetFilePath) & “\temp_document.xml”
If fso.FileExists(tempXmlPath) Then fso.DeleteFile tempXmlPath
‘ 1. Wordを非表示で起動し、FlatOPC(XML)形式に変換して保存
Dim appWord As Object
Dim doc As Object
Set appWord = CreateObject(“Word.Application”)
appWord.Visible = False
appWord.ScreenUpdating = False
On Error GoTo ErrorHandler
Set doc = appWord.Documents.Open(targetFilePath, ReadOnly:=False, Visible:=False)
‘ wdFormatXMLDocument = 11 (Word 2003 XML Document)
doc.SaveAs2 tempXmlPath, 11
doc.Close False
‘ 2. MSXML2.DOMDocument.6.0 を用いてXMLをメモリにロード
Set xmlDoc = CreateObject(“MSXML2.DOMDocument.6.0”)
xmlDoc.Async = False
xmlDoc.SetProperty “SelectionLanguage”, “XPath”
If Not xmlDoc.Load(tempXmlPath) Then
Err.Raise 1000, , “XMLのロードに失敗しました: ” & xmlDoc.parseError.reason
End If
‘ 3. OpenXMLの名前空間を登録(これが動かないとXPathがヒットしない)
xmlDoc.setProperty “SelectionNamespaces”, _
“xmlns:w=’http://schemas.openxmlformats.org/wordprocessingml/2006/main’ ” & _
“xmlns:wx=’http://schemas.microsoft.com/office/word/2003/auxHint'”
‘ 4. 高速DOM置換実行(テキストノードを一括走査)
Dim textNodes As Object
Dim node As Object
Dim replacedCount As Long
‘ w:t (Text) 要素をすべて取得
Set textNodes = xmlDoc.SelectNodes(“//w:t”)
replacedCount = 0
For Each node In textNodes
If InStr(node.Text, targetStr) > 0 Then
node.Text = Replace(node.Text, targetStr, replaceStr)
replacedCount = replacedCount + 1
End If
Next node
‘ 5. 変更したXMLを保存
xmlDoc.Save tempXmlPath
‘ 6. 変換したXMLをWordで開き、元の.docx形式に再保存
Set doc = appWord.Documents.Open(tempXmlPath, Visible:=False)
doc.SaveAs2 targetFilePath, 12 ‘ wdFormatDocumentDefault = 12 (.docx)
doc.Close False
‘ 7. 一時ファイルのクリーンアップ
If fso.FileExists(tempXmlPath) Then fso.DeleteFile tempXmlPath
‘ 終了処理
appWord.Quit False
Set doc = Nothing
Set appWord = Nothing
Set xmlDoc = Nothing
Set fso = Nothing
MsgBox “高速置換が完了しました。” & vbCrLf & _
“置換対象ノード数: ” & replacedCount & ” 箇所” & vbCrLf & _
“処理時間: ” & Format(Timer – startTime, “0.00秒”), vbInformation
Exit Sub
ErrorHandler:
On Error Resume Next
If Not doc Is Nothing Then doc.Close False
If Not appWord Is Nothing Then appWord.Quit False
If fso.FileExists(tempXmlPath) Then fso.DeleteFile tempXmlPath
MsgBox “エラーが発生しました: ” & Err.Description, vbCritical
Set doc = Nothing
Set appWord = Nothing
Set xmlDoc = Nothing
Set fso = Nothing
End Sub
—
4. チーフアーキテクトが教える、実務投入時の「3つの罠」
このアプローチは圧倒的な速度を誇るが、生半可な知識で現場に投入すると致命的なバグを生む。以下の注意点を必ず死守せよ。
① 「分割された文字列(Fragmented Text)」の罠
WordのXML構造において、ユーザーが「ABC」と連続して入力したように見えても、途中で書式変更(太字や文字色変更など)が入ると、XML上は以下のように分割される。
今回のコードのように単純な `//w:t` の置換では、検索文字列が `ABC` の場合、ノードが分かれているためヒットしない。
完全な文字列マッチングを行いたい場合は、段落(`w:p`)単位でテキストを結合・正規化してからDOMに書き戻す、あるいは正規表現をDOMのテキストノードに対して適用する高度な前処理が必要となる。まずは「同一書式内のキーワード置換」という実用的なユースケースから適用することを強く推奨する。
② ファイルロックとデータベース連携の原則
数万ページの文書を扱うシステムでは、このVBAを単体で動かすだけでなく、RDB(SQL ServerやAccessなど)からバッチ処理的に呼び出すケースが多い。
- 排他制御: 処理中のファイルを他のプロセスが掴まないよう、ファイルI/Oエラーのキャッチ(`On Error` によるリトライ機構)を必ず実装すること。
- メモリリークの排除: `MSXML2.DOMDocument` や `Word.Application` はオブジェクト変数を確実に `Nothing` に開放し、COMの参照カウントをゼロに落とせ。これを怠ると、バッチ処理を連続実行した際にメモリが枯渇する。
③ セキュリティとマクロの生存確認
FlatOPC形式へのコンバートおよび再保存の過程で、文書内に埋め込まれたVBAプロジェクト(`.bin` ストリーム)が正しく引き継がれるか、あるいはデジタル署名が失われないかを事前に検証テストすること。純粋なテキスト置換ツールとして割り切り、本文(Body)のみの処理に特化させるのが最も安全である。
—
総括
オブジェクトモデルに依存した甘ったれたコードでは、もはや現代の大規模ドキュメント業務をさばききれない。
「ファイルをパースし、構造を理解し、メモリ上で処理して書き戻す」というコンピュータサイエンスの王道アプローチをVBAに持ち込むことで、あなたの業務自動化ツールは「おもちゃ」から「エンタープライズソリューション」へと昇華する。
極限のパフォーマンスをその手で掴み取ってほしい。
