【テクニカル・上級編】【上級者向け】正規表現による「数式エディタ」内の文字列検索と置換の限界と回避策 – Word VBA解析バイブル

スポンサーリンク

Word数式の深淵を穿つ:Find/Replaceの限界を超えた「XML直叩き」という解法

Word VBAを操る多くの開発者が、ある日突然、絶望の淵に立たされる。「数式(OMath)の中身を正規表現で一括置換したい」という要求だ。

結論から言おう。Wordの `Range.Find` オブジェクトは、数式オブジェクトの内部構造には一切アクセスできない。表面的なテキストの置換は数式を破壊するか、あるいは無視して通り過ぎる。これがWordのメモリモデルにおける「数式」の冷酷な仕様だ。

本稿では、レガシーなVBA環境において、数式を破壊せずに内部文字列を掌握する「XML介入」という禁断の手法を、極限の最適化と共に伝授する。

—

1. なぜFindオブジェクトは数式を無視するのか

Wordにおいて、数式(`OMath`)は「OXML (Office Open XML)」という別世界の住人である。通常の `Find` はWordの `Story` 層を走査するが、数式は独自のコンテナの中に隔離されており、Rangeの検索範囲には含まれない。

これを解決する唯一の正攻法は、Wordファイルを一時的に解凍(または `Flat OPC` 変換)し、XML構造を直接操作することである。しかし、巨大な文書でこれをやるのはメモリの自殺行為だ。

我々は、「Wordのオブジェクトモデルを介してXMLを抽出・置換し、再投入する」というアプローチを取る。

—

2. 実装の指針:DOM解析とメモリの最適化

以下のコードは、数式内部の特定の数式記述をVBAの正規表現エンジン(`VBScript.RegExp`)で叩くためのアーキテクチャだ。

‘ 【必携】メモリリークを許さないための設計指針
‘ WordのCOM参照を保持し続けると、Officeプロセスがゾンビ化する。
‘ 常にSet Nothingを徹底せよ。

Public Sub BatchReplaceInOMaths(targetRegex As String, replacement As String)
Dim doc As Document
Dim oMath As OMath
Dim xmlText As String
Dim regEx As Object

Set doc = ActiveDocument
Set regEx = CreateObject(“VBScript.RegExp”)

‘ 正規表現エンジンの初期化
With regEx
.Global = True
.IgnoreCase = False
.Pattern = targetRegex
End With

‘ 数式オブジェクトの走査
‘ OMathsコレクションはRange単位で扱うのがWordの鉄則
For Each oMath In doc.OMaths
‘ XMLソースを抽出
xmlText = oMath.Range.XML

If regEx.Test(xmlText) Then
‘ 置換処理
xmlText = regEx.Replace(xmlText, replacement)

‘ 【重要】XMLを直接Rangeに再注入する
‘ これにより数式の内部論理を保ったまま置換が完了する
oMath.Range.InsertXML xmlText
End If
Next oMath

‘ 明示的なオブジェクト解放
Set regEx = Nothing
Set doc = Nothing
End Sub

—

3. シニアエンジニアが押さえるべき「罠」

この手法には、いくつかの「伝説級の落とし穴」が存在する。

① XMLの整合性(Well-formedness)

`InsertXML` は強力だが、置換したXMLが崩れていれば、数式は即座に壊れる。特に `m:oMath` タグの前後や、`m:r`(Run)要素を不用意に削除してはならない。正規表現で置換を行う際は、必ずXMLの構造を壊さないようなパターンを構築すること。

② メモリの断片化とパフォーマンス

数式が数千個存在するドキュメントで上記コードを走らせると、`Range.XML` のプロパティ呼び出しがメモリを食いつぶす。

  • 対策: 可能であれば `Application.ScreenUpdating = False` を行い、さらに定期的に `DoEvents` を挟むことで、OS側への制御を戻す必要がある。

③ Windows APIによる強制GC

VBAにはガベージコレクションがない。長時間稼働させるシステムでは、COMの参照カウントが溜まり、メモリリークを起こす。
大規模バッチ処理を行う場合は、`CoFreeUnusedLibraries` を呼び出すAPIラッパーを実装し、ループの区切りで呼ぶのが、伝説的なアーキテクトとしての矜持だ。

If VBA7 Then
Private Declare PtrSafe Function CoFreeUnusedLibraries Lib “ole32.dll” () As Long
Else
Private Declare Function CoFreeUnusedLibraries Lib “ole32.dll” () As Long
End If

—

4. 結び:エンジニアの美学

「数式が検索できないから」と諦めて手作業で置換を行うのは、自動化エンジニアの敗北だ。Wordという巨大なブラックボックスの内部構造(OXML)にまで踏み込み、そのメモリモデルを理解した上で制御を行う。これこそが、レガシーを支配する者の立ち振る舞いである。

このコードは、数式の置換だけでなく、数式内の特定の変数名を一括変換するツールへと容易に進化する。あとは君の正規表現の腕次第だ。

健闘を祈る。Wordの深淵は、まだ掘れる。

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