【テクニカル・上級編】【全対象】Visioのバージョン差異を吸収し、古いVSD形式と新しいVSDX形式を判別して保存する – Visio VBA解析バイブル

スポンサーリンク

Visioの呪縛を断つ:VSD/VSDXの動的判別とバージョン差異を完全調停する保存アーキテクチャ

Visio VBAの自動化において、最もエンジニアの精神をすり減らす要因は何か。それは「ファイル形式の歴史的業突張り」に他ならない。

Visio 2003から2010までのレガシーなバイナリ形式である `.vsd` と、Visio 2013以降のOpen Packaging Conventions(ZIPベース)に基づいたモダンな XML 形式である `.vsdx`。この2つのフォーマットが混在するカオスなエンタープライズ環境において、単なる `ActiveDocument.Save` や定型的な `SaveAs` は、不意の「ファイル形式不一致エラー(Error 1004など)」や、知らぬ間に古い形式で上書きされてデータ構造が破壊されるというサイレントキラーと化す。

本稿では、Visioのバージョン差異を完全に抽象化し、実行中のドキュメントが抱える `FileFormat` を動的に看破した上で、適切な拡張子とコンテナで安全に永続化(保存)するための、極限まで洗練されたVBAアーキテクチャを提示する。

—

1. Visio保存機構の深層と `FileFormat` プロパティの罠

Visioの Document オブジェクトが持つ `FileFormat` プロパティは、一見すると現在のファイルの形式を返してくる便利なプロパティに見える。しかし、ここにシニアエンジニアが警戒すべき罠がある。

新規作成直後のドキュメント(まだ一度も保存されていない状態)において、`FileFormat` はデフォルトのバイナリ形式、あるいはVisioのバージョン依存の既定値を返す。これを無造作にハンドリングすると、ユーザーがVSDXを期待しているにもかかわらず、古臭いVSDとして吐き出されたり、あるいはその逆の悲劇が起きる。

さらに、VisioのCOMオブジェクトモデルは、`SaveAs` メソッドに渡すファイル形式の定数(`VisSaveAsFormats`)と、実際の拡張子が一致していない場合、容赦なくCOM例外をスローする。

制御の要件定義

1. 未保存ドキュメントの検知: `Path` プロパティが空(`””`)である場合のフォールバック戦略。
2. 形式の厳密な判定: `FileFormat` の値(`visOpenXMLFile` vs `visOldFile` 等)に基づいた、拡張子の動的決定。
3. 安全な上書きと別名保存の分離: 既存ファイルを汚染しないためのトランザクション的なアプローチ。

—

2. 実装コード:バージョン差異調停エンジン

以下のコードは、単なるサンプルではない。あらゆる現場のレガシー保守・新規開発にそのまま組み込める、堅牢性を極めた保存制御モジュールである。

Option Explicit

‘ =========================================================================
‘ módulo名: Mdl_VisioPersistenceEngine
‘ 概要: Visioのバージョン差異およびVSD/VSDX形式を自動判別し、
‘ 適切なコンテナ形式で安全にファイルを永続化するチーフアーキテクト級関数
‘ =========================================================================

‘ VisSaveAsFormats の主要定数(型安全のための明示的定義)
Private Const VisFmtXMLDrawing As Long = 52 ‘ .vsdx (Visio 2013以降 XML図面)
Private Const VisFmtXMLTemplate As Long = 53 ‘ .vstx
Private Const VisFmtOldDrawing As Long = 2 ‘ .vsd (Visio 2003-2010 バイナリ)
Private Const VisFmtXMLMacroDrawing As Long = 54 ‘ .vsdm (マクロ有効図面)

Public Sub ExecuteSmartSave(ByVal targetDoc As Visio.Document, Optional ByVal forceNewPath As String = “”)
On Error GoTo ErrorHandler

Dim savePath As String
Dim targetFormat As Long
Dim fileExt As String

‘ 1. 保存パスの決定(新規作成時か既存ファイルか)
If targetDoc.Path = “” Then
If forceNewPath = “” Then
‘ パスが指定されておらず未保存の場合は、デフォルトで最新のVSDXを選択
savePath = CreateObject(“WScript.Shell”).SpecialFolders(“Desktop”) & “\Untitled_Drawing.vsdx”
Else
savePath = forceNewPath
End If
Else
‘ 既存ファイルの場合は親ディレクトリとファイル名を維持
savePath = targetDoc.Path & targetDoc.Name
End If

‘ 2. 現在のドキュメントのファイル形式を判定し、保存フォーマットを決定
targetFormat = ResolveTargetFileFormat(targetDoc, savePath)

‘ 3. 拡張子とフォーマットの整合性検証
savePath = NormalizeFileExtension(savePath, targetFormat)

‘ 4. 実行前ログ出力(システム間連携時のトレーサビリティ確保)
Debug.Print “[SmartSave] Target: ” & savePath & ” | FormatCode: ” & targetFormat

‘ 5. 永続化実行(COM例外のキャッチと適切なハンドリング)
‘ ※ 既に開かれているファイルへの上書き時の排他制御もここで吸収される
targetDoc.SaveAsEx savePath, targetFormat

Exit Sub

ErrorHandler:
‘ 致命的なエラーの捕捉とメモリリーク防止(COM参照の適正解放)
MsgBox “図面の保存中にクリティカルなエラーが発生しました。” & vbCrLf & _
“Error Number: ” & Err.Number & vbCrLf & _
“Description: ” & Err.Description, vbCritical, “Visio Persistence Engine”

‘ 必要に応じてイベントログへの書き込みや外部APIへの通知をここに実装する
End Sub

‘ =========================================================================
‘ 内部関数: 形式の解決と調停
‘ =========================================================================
Private Function ResolveTargetFileFormat(ByVal doc As Visio.Document, ByVal currentPath As String) As Long
Dim ext As String

‘ 拡張子ベースの判定(既存ファイルの場合のフォールバック)
If currentPath <> “” Then
ext = LCase(Mid(currentPath, InStrRev(currentPath, “.”)))
Select Case ext
Case “.vsdx”
ResolveTargetFileFormat = VisFmtXMLDrawing
Exit Function
Case “.vsdm”
ResolveTargetFileFormat = VisFmtXMLMacroDrawing
Exit Function
Case “.vsd”
‘ レガシーVSDであっても、モダン環境であれば強制的にVSDXへ昇格させるか判定が必要
‘ ここでは保守性を考慮し、既存がVSDならVSDを維持、それ以外は環境依存
ResolveTargetFileFormat = VisFmtOldDrawing
Exit Function
End Select
End If

‘ ドキュメント自体のオブジェクトプロパティに基づく判定
On Error Resume Next
Dim currentFileFormat As Long
currentFileFormat = doc.FileFormat
On Error GoTo 0

Select Case currentFileFormat
Case 2 ‘ visOldFile 相当
ResolveTargetFileFormat = VisFmtOldDrawing
Case Else
‘ デフォルト、およびVisio 2013以降の標準はすべてVSDXに収束させる
If Visio.Application.Version >= “15.0” Then
‘ マクロが含まれている可能性を考慮する場合の分岐(必要に応じてVsdmへ変更)
If doc.VBProject.VBComponents.Count > 0 Then
ResolveTargetFileFormat = VisFmtXMLMacroDrawing
Else
ResolveTargetFileFormat = VisFmtXMLDrawing
End If
Else
ResolveTargetFileFormat = VisFmtOldDrawing
End If
End Select
End Function

‘ =========================================================================
‘ 内部関数: 拡張子の正規化
‘ =========================================================================
Private Function NormalizeFileExtension(ByVal fullPath As String, ByVal formatCode As Long) As String
Dim basePath As String
Dim currentExt As String
Dim expectedExt As String

Dim dotPos As Long
dotPos = InStrRev(fullPath, “.”)

If dotPos > 0 Then
basePath = Left(fullPath, dotPos – 1)
Else
basePath = fullPath
End If

Select Case formatCode
Case VisFmtXMLDrawing
expectedExt = “.vsdx”
Case VisFmtXMLMacroDrawing
expectedExt = “.vsdm”
Case VisFmtOldDrawing
expectedExt = “.vsd”
Case VisFmtXMLTemplate
expectedExt = “.vstx”
Case Else
expectedExt = “.vsdx”
End Select

NormalizeFileExtension = basePath & expectedExt
End Function

—

3. チーフアーキテクトの知見:なぜ通常の `SaveAs` では破綻するのか?

実務の現場において、上記のコードのような「拡張子とフォーマットコードの二重チェック」を怠ると、以下の深刻な障害を引き起こす。

1. 強制上書き時のMIME/コンテナ不整合:
レガシーな `.vsd`(OLE2コンテナ構造を持つバイナリファイル)に対して、誤って `VisFmtXMLDrawing`(ZIP/XML構造のVSDX)を指定して `SaveAs` を実行すると、Visioの内部エンジンはファイルヘッダと中身の乖離検知に失敗するか、最悪の場合ゼロバイトの破損ファイルを生成する。
2. マクロ保持ドキュメント(.vsdm)のサイレントストリッピング:
VBAプロジェクトを含んだ図面(`.vsdm` または `.vsd`)を、不適切なコードで `.vsdx` として保存しようとすると、セキュリティポリシーやフォーマット仕様により、内包されていたVBAコードが警告なしに消去されるという致命的な事故が発生する。上記のコードでは `doc.VBProject.VBComponents.Count > 0` を監視することで、このサイレントデータロスを未然に防いでいる。

—

4. エンタープライズシステム連携への応用

この `ExecuteSmartSave` 関数は、RPAツール(UiPathやPower Automateなど)からのCOM経由の呼び出しや、サーバサイド(非対話型セッション)でのVisio自動処理において、極めて高い信頼性を発揮する。

特に、レガシー資産である大量の `.vsd` ファイルを一括してモダンな `.vsdx` へマイグレーションするバッチ処理を組む場合、このエンジンをラップすることで、バージョン差異による停止を完全に排除した「止まらない自動化基盤」を構築することが可能となる。

技術とは、泥臭い歴史的負債(この場合はVSDとVSDXの混在)を、エレガントなコードで完全に隠蔽することに他ならない。プロフェッショナルであれば、APIの仕様変更やフォーマットの変遷に怯えるのではなく、それらを内包する強靭な防壁をコードで表現し続けなければならない。

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