【実務・中級編】【上級者向け】段落の「フォント」設定を、Wordの「スタイル」の継承関係を考慮して動的に計算する – Word VBA解析バイブル

スポンサーリンク

【上級者向け】Word VBAを掌握する極限の知見:スタイル継承を完全解剖し、最終フォントを動的算出演算するエンジン

Wordの自動化において、最も多くの開発者が挫折し、そして最も多くの「バグの温床」となるのがスタイルの継承関係とフォント設定の解決だ。

「段落の `.Range.Font.Name` を取得したら `Null` が返ってきた」
「見出しスタイルを変更したはずなのに、なぜか直Overrides(直接書式)が優先されて意図した見た目にならない」

君たちはこうした現象に直面し、泥臭い `If` 文の要塞を築いていないだろうか?
本記事では、Wordの内部アーキテクトがどのようなロジックでスタイルを解決しているかを解き明かし、「スタイルの継承元(ベーススタイル)を再帰的に遡り、最終的にどのフォントが描画されるかを完全に特定する解析エンジン」のプロダクションコードを授与する。

1. なぜ「愚直なプロパティ取得」は破綻するのか?

Wordの `Font` オブジェクトや `ParagraphFormat` オブジェクトは、「未設定(Null)」という状態を持つ。
ある段落のフォント名を取得しようとしたとき、その段落に直接フォントが指定されていなければ、Wordは適用されている「スタイル」を見に行く。さらに、そのスタイルが別のスタイルをベースにしている場合、親スタイル、さらにその親スタイルへと遡っていく。

これをVBAの浅い知識で実装しようとするとこうなる:

‘ 【アンチパターン】絶対にやってはいけない実装
Dim fontName As String
If rng.Font.Name <> “” Then
fontName = rng.Font.Name
Else
‘ スタイルを見る? いや、ベーススタイルはどうする?
fontName = rng.Style.Font.Name
End If

このコードは、多段継承(例: 「箇条書き」->「標準」->「デフォルト」)や、文字スタイルと段落スタイルの複合的なオーバーライド構造の前には無力だ。結果、出力ドキュメントのフォントがバラバラになる不具合が多発する。

プロのエンジニアなら、Wordのオブジェクトモデルの挙動を信じるな。「継承チェーンをコードで完全再現し、確定値を数学的に算出する」アプローチを取るべきなのだ。

2. 堅牢な設計:スタイルチェーンの再帰的解決アルゴリズム

最終的なフォント設定(フォント名、サイズ、太字など)を特定するためには、以下の優先順位(スコープ)を厳密にコード上で再現する必要がある。

1. 直接書式(Direct Formatting): レンジや段落に直接施された装飾。これが最優先。
2. 文字スタイル(Character Style): 段落内に適用されている文字レベルのスタイル。
3. 段落スタイル(Paragraph Style): 段落自体のスタイル。ここからが本番で、`BaseStyle` プロパティを再帰的に `Normal` スタイル(またはベースがなくなるまで)辿る必要がある。

このアルゴリズムを実装した、実務投入可能なクラスモジュール(または標準モジュール群)のコードを提示する。

3. プロダクションコード:スタイル継承解決エンジン

以下のコードは、指定した段落(またはレンジ)において、最終的にどのフォント名とサイズが適用されるかを、スタイルの継承を遡って完璧に算出するエンジンである。

実装コード(標準モジュール)

Option Explicit

‘ ==============================================================================
構造体: 解決済みフォント情報
‘ ==============================================================================
Type ResolvedFont
Name As String
Size As Single
IsBold As Long ‘ -1: True, 0: False, 999: 未定
SourceType As String ‘ どこから継承されたかのログ
End Type

‘ ==============================================================================
‘ @Title: GetUltimateFontSettings
‘ @Description: スタイルの継承ツリーを再帰的に解析し、最終的なフォント設定を算出する
‘ ==============================================================================
Public Sub ExecuteFontResolutionDemo()
Dim targetPara As Paragraph
Set targetPara = Selection.Paragraphs(1)

Dim result As ResolvedFont
result = ResolveEffectiveFont(targetPara.Range)

‘ 結果出力
Debug.Print “=== フォント解決結果 ===”
Debug.Print “最終フォント名: ” & result.Name & ” (由来: ” & result.SourceType & “)”
Debug.Print “最終サイズ : ” & result.Size & ” pt”
Debug.Print “太字設定 : ” & result.IsBold
End Sub

Public Function ResolveEffectiveFont(ByVal rng As Range) As ResolvedFont
Dim res As ResolvedFont
‘ 初期値の設定(未解決状態)
res.Name = “”
res.Size = 0
res.IsBold = 999
res.SourceType = “Unresolved”

‘ —————————————————————-5
‘ 1. 直接書式(Direct Formatting)のチェック
‘ —————————————————————-季
‘ ※ WordのVBAでは、未設定の場合 vbNullString や 0 が返るが、
‘ 厳密には Font オブジェクトのプロパティが独自の状態を持つ。
On Error Resume Next
Dim directFontName As String
directFontName = rng.Font.Name

Dim directSize As Single
directSize = rng.Font.Size

Dim directBold As Long
directBold = rng.Font.Bold
On Error GoTo 0

If directFontName <> “” Then
res.Name = directFontName
res.SourceType = “Direct Formatting”
End If

If directSize > 0 Then
res.Size = directSize
End If

If directBold = msoTrue Or directBold = msoFalse Then
res.IsBold = directBold
End If

‘ —————————————————————-
‘ 2. スタイル階層の解決(Directで決まっていない項目を補完)
‘ —————————————————————-
Dim currentStyle As Style
Set currentStyle = rng.Style

Call TraceStyleHierarchy(currentStyle, res)

‘ —————————————————————-
‘ 3. それでも決まらない場合のドキュメントデフォルトフォントフォールバック
‘ —————————————————————-
If res.Name = “” Then
res.Name = rng.Document.Styles(wdStyleNormal).Font.Name
res.SourceType = “Document Normal Style (Fallback)”
End If

If res.Size <= 0 Then res.Size = rng.Document.Styles(wdStyleNormal).Font.Size End If ResolveEffectiveFont = res End Function ' ============================================================================== ' スタイルツリーを再帰的に遡るコアプロシージャ ' ============================================================================== Private Sub TraceStyleHierarchy(ByVal st As Style, ByRef res As ResolvedFont) If st Is Nothing Then Exit Sub ' フォント名が未解決で、かつスタイルのフォント名が設定されている場合 If res.Name = "" Then On Error Resume Next If st.Font.Name <> “” Then
res.Name = st.Font.Name
res.SourceType = “Style: ” & st.NameLocal
End If
On Error GoTo 0
End If

‘ サイズが未解決の場合
If res.Size <= 0 Then On Error Resume Next If st.Font.Size > 0 Then
res.Size = st.Font.Size
End If
On Error GoTo 0
End If

‘ 太字が未解決の場合
If res.IsBold = 999 Then
On Error Resume Next
Dim b As Long
b = st.Font.Bold
If b = msoTrue Or b = msoFalse Then
res.IsBold = b
End If
On Error GoTo 0
End If

‘ 終了条件:すべてのパラメータが解決されたか、ベーススタイルがない場合
If (res.Name <> “” And res.Size > 0 And res.IsBold <> 999) Then
Exit Sub
End If

‘ 再帰処理:BaseStyleを遡る
On Error Resume Next
Dim parentStyle As Style
Set parentStyle = st.BaseStyle
On Error GoTo 0

If Not parentStyle Is Nothing Then
‘ 無限ループ防止(Wordの標準では稀だが、破損ドキュメント対策)
If parentStyle.NameLocal <> st.NameLocal Then
Call TraceStyleHierarchy(parentStyle, res)
End If
End If
End Sub

4. 現場で活きる!データベース・外部連携時の注意点

このフォント解析エンジンを、単なるマクロにとどまらず、「社内規程チェックツール」「DBへのドキュメント構造インポートバッチ」などのエンタープライズ用途に組み込む場合、以下のアーキテクチャ上の罠に注意せよ。

① 画面描画(ScreenUpdating)の完全抑制

再帰処理や大量の段落スキャンを行う際、WordのUIが追従すると実行速度が100分の1以下に低下する。
処理の開始前には必ず以下を挟み、例外処理の確実な復旧を担保すること。

Application.ScreenUpdating = False
Application.DisplayAlerts = wdAlertsNone
Application.EnableEvents = False
‘ — 処理本体 —
‘ 復旧
Application.ScreenUpdating = True
Application.DisplayAlerts = wdAlertsAll
Application.EnableEvents = True

② 破損ドキュメント(Corrupted Document)への耐性

長年編集されたWordファイルは、スタイルの循環参照(AスタイルがBをベースにし、BがAをベースにする異常事態)を引き起こしていることがある。
先ほどのコードでは `parentStyle.NameLocal <> st.NameLocal` でガードを入れているが、実務のDB連携バッチでは、さらに再帰呼び出しの深度カウンター(Max Depth = 10 など)を設けるのが、プログラマとしての正しい防衛的プログラミングである。

③ データベースへの出力フォーマット

解析したフォント名やサイズをSQL等でRDBに格納する場合、`res.SourceType`(どこでそのフォントが定義されていたか)のログを一緒に保持することを強く推奨する。
「なぜこの段落はこのフォントになっているのか?」という問い合わせに対し、監査証跡として「直接書式によるオーバーライド」なのか「親スタイルからの継承」なのかを即座に特定できるため、システム保守性が劇的に向上する。

5. チーフアーキテクトからの総括

Word VBAは、一見するとレガシーで場当たり的なコードが許容される世界に見える。しかし、ドキュメントの構造化、スタイルの継承、そしてオブジェクトモデルの裏側にあるコンテキストを正しく理解すれば、極めて堅牢でスケーラブルなエンジニアリングが可能だ。

「動けばいい」のフェーズはもう終わりだ。
君たちが構築する自動化ツールは、ドキュメントの構造を正しく理解し、どんなに複雑に劣化したファイルであっても正確に真実を導き出す「知的なエンジン」でなければならない。

このコードをベースに、自社のドキュメントワークフローを極限まで自動化・最適化して見せてほしい。健闘を祈る。

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