【テクニカル・上級編】【上級者向け】XML形式のVisioファイル(VSDX)を直接操作し、ページ設定を高速に一括置換する手法 – Visio VBA解析バイブル

スポンサーリンク

【Visio VBA極限の知見】数千ページを秒殺する:VSDX内部XML直叩きによるページ設定一括置換アーキテクチャ

Visioのオブジェクトモデルは、時として開発者の牙を剥く。
`ActiveDocument.Pages`をループさせ、1ページずつ`PageSheet`の`PageWidth`や`PageHeight`を書き換える――そんな古典的でナイーブなアプローチを取っていないだろうか。

数千ページ規模のドキュメントにおいて、VBAからCOM経由でVisioのドキュメントオブジェクトを操作することは、「自らドロ舟に乗り込んで嵐の海に出る」ようなものだ。画面の再描画、Undoスタックの肥大化、COM境界を跨ぐオーバーヘッド、そして容赦なく訪れるメモリリーク。数千ページの変更に数十分、下手をすればOutOfMemoryでクラッシュする。

我々はエンジニアである。Visioの本質が「ZIPで固められたXML群(VSDX)」であるならば、オブジェクトモデルなどというまわりくどいラッパーをバイパスし、直接XMLの急所を突けばいい。

今回は、VSO(Visio Automation Object)を完全に捨て去り、VSDXの内部XML(具体的には`pages/page.xml`および`document.xml`)をVBA(および必要に応じたWindows API/COMコンポーネント)から直接高速書き換えする、極限のパフォーマンス・チューニング手法を解説する。

—

1. なぜオブジェクトモデルは「遅い」のか?(根本原因の解剖学)

Visio VBAでページ設定を変更する際、背後では以下の処理が実行されている。

1. COMマージンとプロセス間通信: VBAランタイムからVisioプロセスへの命令発行ごとに、RPC(リモートプロシージャコール)のコストが発生する。
2. ShapeSheetの再計算エンジン(Calc Engine): 1つのプロパティを変更するたびに、関連する数式やセルの依存関係ツリーが再評価される。数千ページあれば、このツリー評価だけでCPUコアが焼き切れる。
3. トランザクション管理: すべての変更はUndoスタックに積まれる。

これらを無効化するために、UIの画面描画停止(`ScreenUpdating = False`)やイベントの無効化(`EventEnabled = False`)を行うのが一般的な「初級〜中級」の対策だ。しかし、オブジェクトモデルを使う限り、Calc EngineとCOMの呪縛からは逃れられない。

解決策:VSDXの直接解凍、DOM/XPathによる一括置換、そして再圧縮。
このパイプラインを構築すれば、数千ページの用紙サイズ・向き・余白の変更を、数秒(O(N)から極限まで最適化されたI/Oバウンド処理)で完了させることが可能になる。

—

2. VSDXファイル構造の急所

VSDXの実体はOpen Packaging Conventions (OPC) に準拠したZIPアーカイブである。ページ設定に関連する実体は、主に以下のパーツに散らばっている。

  • `/visio/document.xml`: ドキュメント全体のデフォルト設定やマスター情報。
  • `/visio/pages/page1.xml`, `page2.xml` …: 各ページのルート要素(`` または ``)に、ページの物理サイズ(`PageWidth`, `PageHeight`)や印刷設定が保持されている。

ページサイズを変更する場合、各`page.xml`内の``セルフ内にあるセル(Cell)要素の値を書き換える必要がある。

—

3. 実装:VBAによるVSDXダイレクト・パッチング・エンジン

以下のコードは、外部ライブラリ(Microsoft XML v6.0 および Shell.Application)を駆使し、Visioを起動することなく、指定したVSDX内の全ページの用紙サイズ(例: A4縦からA3横へ)をミリ秒単位で書き換える実用モジュールである。

Option Explicit

‘ =========================================================================
‘ 建築家レベルの極限最適化:VSDXページ設定一括置換エンジン
‘ 前提条件: ターゲットのVSDXファイルは閉じられていること(排他制御回避)
‘ =========================================================================
Public Sub UltraFastPageSetupTransformer(ByVal vsdxPath As String, ByVal targetWidth As Double, ByVal targetHeight As Double)
Dim fso As Object
Dim tempDir As String
Dim zipFile As String

Set fso = CreateObject(“Scripting.FileSystemObject”)

If Not fso.FileExists(vsdxPath) Then
MsgBox “指定されたファイルが存在しません: ” & vsdxPath, vbCritical
Exit Sub
End If

‘ 1. 一意のテンポラリディレクトリを作成
tempDir = fso.GetSpecialFolder(2) & “\VisioMod_” & Format(Now, “yyyymmddhhmmss”) & “_” & Int(Rnd 10000)
fso.CreateFolder tempDir

On Error GoTo ErrorHandler

‘ 2. VSDX(実体はZIP)をテンポラリに展開
‘ Shell.Applicationを利用した高速ZIP展開
Dim sa As Object
Set sa = CreateObject(“Shell.Application”)

‘ ファイルをいったん.zipにリネームして解凍するか、直接CopyHereを使う
Dim zipCopyPath As String
zipCopyPath = tempDir & “\source.zip”
fso.CopyFile vsdxPath, zipCopyPath, True

Dim items As Object
Set items = sa.Namespace(zipCopyPath).Items
sa.Namespace(tempDir & “\extracted”).CopyHere items

‘ 3. 展開されたXML群を一括走査・置換 (DOM / XPath)
Call ProcessPageXmls(tempDir & “\extracted\visio\pages”, targetWidth, targetHeight)

‘ 4. 再圧縮してVSDXに戻す
‘ 既存のVSDXを削除または上書き
Kill vsdxPath

‘ 空のZIPを作成して格納
Dim stream As Object
NewZip (vsdxPath)

Dim extractedPath As String
extractedPath = tempDir & “\extracted\”

‘ フォルダ内を再圧縮(Shell.Application経由)
Dim destNamespace As Object
Set destNamespace = sa.Namespace(vsdxPath)

Dim srcFolderItems As Object
Set srcFolderItems = sa.Namespace(extractedPath).Items

‘ 圧縮処理の完了を少し待つ(非同期対策)
destNamespace.CopyHere srcFolderItems

‘ 処理待ちの簡易スリープ(環境依存によるI/O競合を防ぐ)
Dim startTime As Single
startTime = Timer
Do While Timer – startTime < 1.5 DoEvents Loop CleanUp: ' 5. メモリ解放とテンポラリクリーンアップ On Error Resume Next If fso.FolderExists(tempDir) Then fso.DeleteFolder tempDir, True Set fso = Nothing Set sa = Nothing MsgBox "VSDXのページ設定一括置換が完了しました。", vbInformation Exit Sub ErrorHandler: MsgBox "致命的なエラーが発生しました: " & Err.Description, vbCritical Resume CleanUp End Sub ' --- 内部XMLの書き換えコアロジック (MSXML2.DOMDocument60) --- Private Sub ProcessPageXmls(ByVal pagesDir As String, ByVal w As Double, ByVal h As Double) Dim fso As Object Dim folder As Object Dim file As Object Dim xmlDoc As Object Dim node As Object Dim cellNodes As Object Set fso = CreateObject("Scripting.FileSystemObject") If Not fso.FolderExists(pagesDir) Then Exit Sub Set folder = fso.GetFolder(pagesDir) For Each file In folder.Files If LCase(fso.GetExtensionName(file.Name)) = "xml" And Not (LCase(file.Name) Like "masters") Then Set xmlDoc = CreateObject("MSXML2.DOMDocument.6.0") xmlDoc.Async = False xmlDoc.Load file.Path If xmlDoc.ParseError.ErrorCode = 0 Then ' 名前空間の解決を設定 (Visio XMLスキーマ) xmlDoc.SetProperty "SelectionNamespaces", "xmlns:v='http://schemas.microsoft.com/office/visio/2012/main'" ' PageSheet内の PageWidth および PageHeight セルをXPathで直接ヒットして置換 ' 例:
Set cellNodes = xmlDoc.SelectNodes(“//v:PageSheet/v:Cell[@N=’PageWidth’ or @N=’PageHeight’]”)

Dim cellNode As Object
For Each cellNode in cellNodes
If cellNode.getAttribute(“N”) = “PageWidth” Then
cellNode.setAttribute “V”, CStr(w)
ElseIf cellNode.getAttribute(“N”) = “PageHeight” Then
cellNode.setAttribute “V”, CStr(h)
End If
Next cellNode

‘ 変更を上書き保存
xmlDoc.Save file.Path
End If

‘ オブジェクトの明示的解放(メモリリーク防止)
Set xmlDoc = Nothing
End If
Next file
End Sub

‘ — 空のZIPファイルを生成するヘルパー —
Private Sub NewZip(ByVal path As String)
Dim fso As Object
Set fso = CreateObject(“Scripting.FileSystemObject”)

Dim ts As Object
Set ts = fso.CreateTextFile(path, True)
‘ ZIPファイルの空ヘッダ (PK0506…)
ts.Write Chr(80) & Chr(75) & Chr(5) & Chr(6) & String(18, Chr(0))
ts.Close
Set ts = Nothing
Set fso = Nothing
End Sub

—

4. チーフアーキテクトが教える「現場の罠」と回避策

この手法は圧倒的に高速だが、レガシー環境や大規模システム連携において、以下の「地雷」を踏む可能性があるので注意せよ。

1. 単位系の罠(Visioの内部単位は「インチ」)

VisioのXML内部(ShapeSheetの`V`属性値)は、ユーザーがどんな単位(ミリメートル、センチメートル)で設定していようとも、原則としてインチ(Inches)で保持されている。
もしミリメートル単位で値を渡したい場合は、コード側で `25.4` で割るなどのスケーリングを忘れてはならない。
(例: A4縦 = 210mm × 297mm -> 幅: `210 / 25.4 = 8.2677…`, 高さ: `297 / 25.4 = 11.6929…`)

2. 排他制御とファイルロック

VSDXがネットワークドライブ上にあり、他のユーザーやバックグラウンドプロセスが掴んでいる場合、`Kill` や `CopyFile` で `Permission Denied` が発生する。
システム間連携において本手法を組み込む場合は、処理対象ファイルをローカルの高速なSSDストレージ(例: `%TEMP%`)に一度退避させてからパッチを当て、最終的に元の場所へアトミックに差し替える設計が必須である。

3. スキーマバージョンの差異

Visio 2013以降のVSDXフォーマット(`http://schemas.microsoft.com/office/visio/2012/main`)であれば上記のXPathで完全に動作する。しかし、Visio 2010以前のXML図面(VDX)や独自のカスタムプロパティが大量に紐付くファイルでは、名前空間URIやノード構造が異なる場合がある。必ず事前の検証環境でパース結果をテストすること。

—

5. 総括

VBAは「おもちゃの言語」ではない。背後にあるアーキテクチャの制約を理解し、COMの表層を剥ぎ取って下層のXMLレイヤーを直接支配下置くとき、VBAは真の「エンタープライズ・オートメーション・ツール」へと変貌する。

数千ページの図面生成・一括置換に絶望しているエンジニア諸君。今すぐオブジェクトモデルのループを捨て、XMLダイレクト・パッチングを導入せよ。圧倒的な速度の向こう側で、システムは君の意のままに動くだろう。

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