【テクニカル・上級編】Wordの『ストーリー範囲』を再考する:MainTextStory以外のテキストを安全に一括取得する – Word VBA解析バイブル

スポンサーリンク

Wordの『ストーリー範囲』を再考する:MainTextStory以外のテキストを安全に一括取得する

Word VBAの自動化において、多くの開発者が最初に直面する絶望は、「`ActiveDocument.Content.Text` でドキュメント全体のテキストが取得できない」という事実だ。

`ActiveDocument.Content` は、あくまで `wdMainTextStory`(本文ストーリー)を指しているに過ぎない。ヘッダー、フッター、脚注、文末脚注、そしてドキュメントのレイアウトを縦横無尽に歪めるテキストボックスや描画キャンバス(`Shape` / `InlineShape` 内のストーリー)は、完全に無視される。

エンタープライズ環境における文書解析や機密情報のマスキング、一括置換処理において、一部のテキストを取りこぼすことはシステム障害を意味する。

今回は、Wordのメモリ構造とオブジェクトモデルの深層に踏み込み、全てのストーリー範囲を安全かつ極限のパフォーマンスで走破するアーキテクチャを解説する。

1. Wordの「ストーリー(Story)」概念の深層理解

Wordドキュメントは、単一の連続したテキストストリームではない。内部的には複数の独立したテキストコンテナである「Story(ストーリー)」の集合体として構築されている。

VBAの `WdStoryType` 列挙体を見れば、その多様性が理解できる。

  • `wdMainTextStory` (1): 本文
  • `wdPrimaryHeaderStory` (3): 偶数・奇数ページのヘッダー(通常)
  • `wdPrimaryFooterStory` (6): 偶数・奇数ページのフッター(通常)
  • `wdFootnotesStory` (2): 脚注
  • `wdEndnotesStory` (4): 文末脚注
  • そして、最難関となる Shape(テキストボックスや吹き出し) 内のストーリー群

これらは単に `Document.StoryRanges` コレクションをループするだけでは完全に網羅されない。なぜなら、`Shape` や `InlineShape` 内に存在するテキストは、個別の `TextFrame` を経由するか、あるいはドキュメント全体に散らばる `SubDocument` やネストされた図形群を再帰的に走査しなければ到達できない領域だからだ。

2. 実装パターン:すべてのストーリーを安全に網羅するエンジン

以下に、本文からヘッダー、フッター、そして悪名高い `Shape` 内のテキストまでを漏らさず取得・走査するための、プロダクション品質のVBAコードを提示する。

オブジェクトの解放(メモリ最適化)と、COMの参照リークを防ぐための厳格な変数管理を行っている点に注目してほしい。

Option Explicit

‘ ==============================================================================
‘ 処理名: 総合ストーリー走査エンジン
‘ 概要 : 本文、ヘッダー/フッター、脚注、そしてShape(テキストボックス等)内の
‘ 全テキストを安全に抽出し、一括処理または解析を行う。
‘ ==============================================================================
Public Sub ExecuteDeepStoryTraversal()
Dim doc As Document
Set doc = ActiveDocument

‘ 画面描画と警告を抑制し、COMのIUnknown通信コストと再描画の負荷を極限まで削減
With Application
.ScreenUpdating = False
.DisplayAlerts = wdAlertsNone
.OptimumMemoryUsage = True ‘ メモリ最適化のヒント
End With

On Error GoTo ErrorHandler

‘ 1. 標準ストーリー(Main, Headers, Footers, Footnotes等)の走査
Call TraverseStandardStories(doc)

‘ 2. シェイプ・テキストボックス群の走査(再帰的アプローチ)
Call TraverseShapeStories(doc)

MsgBox “すべてのストーリー範囲の走査が完了しました。”, vbInformation, “チーフアーキテクト通知”

ErrorHandler:
If Err.Number <> 0 Then
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “System Error”
End If

‘ 確実な環境復元
With Application
.ScreenUpdating = True
.DisplayAlerts = wdAlertsAll
End With

‘ オブジェクト変数の明示的解放
Set doc = Nothing
End Sub

‘ ——————————————————————————
‘ 標準ストーリーの走査
‘ ——————————————————————————
Private Sub TraverseStandardStories(ByVal targetDoc As Document)
Dim rngStory As Range
Set rngStory = targetDoc.Content

Do While Not (rngStory Is Nothing)
‘ 各ストーリーに対するビジネスロジックをここに記述
‘ 例: ログ出力や特定キーワードの置換
ProcessRangeText rngStory

‘ 次のリンクされたストーリーへ移動
Set rngStory = rngStory.NextStoryRange
Loop

‘ 明示的な解放
Set rngStory = Nothing
End Sub

‘ ——————————————————————————
‘ シェイプ(テキストボックス・吹き出し等)内のストーリー走査
‘ ——————————————————————————
Private Sub TraverseShapeStories(ByVal targetDoc As Document)
Dim shp As Shape
Dim inlineShp As InlineShape
Dim rngShp As Range

‘ 1. InlineShape(インライン配置された図形)の処理
For Each inlineShp in targetDoc.InlineShapes
If inlineShp.Type = wdShapeTypeCanvas Or inlineShp.HasTextFrame Then
If inlineShp.TextFrame.HasText Then
Set rngShp = inlineShp.TextFrame.TextRange
ProcessRangeText rngShp
End If
End If
Set inlineShp = Nothing
Next inlineShp

‘ 2. 通常の Shape(フローティング図形、テキストボックス等)の処理
For Each shp in targetDoc.Shapes
ProcessShapeRecursive shp
Set shp = Nothing
Next shp

Set rngShp = Nothing
End Sub

‘ ——————————————————————————
‘ シェイプの再帰的走査(グループ化された図形やネスト構造に対応)
‘ ——————————————————————————
Private Sub ProcessShapeRecursive(ByVal currentShp As Shape)
Dim subShp As Shape
Dim rngShp As Range

‘ グループ化されている場合の再帰処理
If currentShp.Type = msoGroup Then
For Each subShp in currentShp.GroupItems
ProcessShapeRecursive subShp
Set subShp = Nothing
Next subShp
Else
‘ テキストフレームを持っているか判定
On Error Resume Next
If currentShp.HasTextFrame Then
If currentShp.TextFrame.HasText Then
Set rngShp = currentShp.TextFrame.TextRange
ProcessRangeText rngShp
End If
End If
On Error GoTo 0
End If

Set rngShp = Nothing
End Sub

‘ ——————————————————————————
‘ テキスト処理のコアロジック(プレースホルダ)
‘ ——————————————————————————
Private Sub ProcessRangeText(ByRef rng As Range)
‘ ————————————————————————–
‘ 【重要】ここに実際の置換や解析ロジックを記述する
‘ 例: Debug.Print rng.Text
‘ ————————————————————————–

‘ 例として機密ワード「Confidential」を「RESTRICTED」に置換する場合:
With rng.Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = “Confidential”
.Replacement.Text = “RESTRICTED”
.Forward = True
.Wrap = wdFindStop
.Format = False
.Execute Replace:=wdReplaceAll
End With
End Sub

3. チーフアーキテクトが指摘する「罠」とパフォーマンス最適化

上記のコードを実務の巨大なWordファイル(数百ページにおよぶ仕様書や契約書など)に適用する際、以下のアーキテクチャ上の懸念点を知らなければ、メモリリークや極端な速度低下を引き起こす。

① `Range.NextStoryRange` の挙動の癖

`StoryRanges` コレクションを直接イテレートするよりも、`Document.Content` から開始して `NextStoryRange` を辿る方が、リンクされたヘッダー/フッターのストーリーを網羅しやすい。しかし、Wordのバージョン(Word 2016以降、Microsoft 365など)によっては、一部のストーリーが正しくリンクチェーンに含まれないケースがある。そのため、`Shapes` や `InlineShapes` の走査を完全に分離して二重担保する必要がある。

② COMオブジェクトの解放とガベージコレクション

VBAはCOMベースの言語であり、内部でCLR(共通言語ランタイム)のような高度なメモリ管理を行わない。
`For Each` ループ内で取得したオブジェクト(`Shape`, `InlineShape`, `Range`)は、ループのスコープを抜けてもVBAのランタイムが即座にメモリを解放するとは限らない。特に巨大なドキュメントでは、ループ内で明示的に `Set xxx = Nothing` を記述し、VBAの参照カウンタを即座にデクリメントすることが、Wordプロセスの肥大化(メモリリーク)を防ぐ唯一の防衛策となる。

③ 画面描画・イベントの完全遮断

大規模な一括置換を行う際、`Application.ScreenUpdating = False` だけでは不十分な場合がある。`Application.DisplayAlerts = wdAlertsNone` や、可能であればバックグラウンドでのドキュメントオープン(`Visible:=False`)を組み合わせることで、GDIリソースの無駄な消費を防ぎ、処理速度を最大で10倍以上に跳ね上げることができる。

4. 結言:レガシーの殻を破る自動化の極み

Word VBAは「古い技術」として片付けられがちだが、企業文書の自動処理において、そのオブジェクトモデルの深さは依然として強力無比である。

本文(`wdMainTextStory`)の表面を撫でるだけのコードで満足しているうちは、真の自動化エンジニアとは言えない。今回解説した「すべてのストーリー範囲を捉える網羅的アプローチ」をマスターすることで、いかなる複雑なレイアウトの文書であっても、一物一価の正確さでコントロールすることが可能となる。

コードの海を恐れず、メモリの隅々まで支配せよ。それこそが、プロフェッショナル・VBAエンジニアの領域である。

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