【型安全なメタデータ管理】`CustomDocumentProperties`を極める!PowerPointメタデータ操作ライブラリ
開発現場でよくある悪夢を語ろう。
社内データベースや外部APIと連携するPowerPoint自動化ツールを作っているときだ。「ファイルのバージョン」「承認フラグ」「最終更新タイムスタンプ」といったメタデータを、素朴に`Presentation.Tags`や野良のテキストボックスへ適当に放り込んでいる。
結果はどうなるか? 「ある日突然、型変換エラーでバッチが死ぬ」。
文字列として保存されたデータを`CDate`で日付に戻そうとした瞬間、誰かが手動で書き換えた「未定」という文字列がパンドラの箱を開ける。真偽値のつもりで入れた `”True”` と `True` の迷宮。数値が空文字になっている絶望。
プロフェッショナルな業務自動化エンジニアなら、こうした泥臭い型的不整合のトラブルを根本から断たなければならない。
PowerPointには、ファイル自体にメタデータを強固に埋め込むための公式な仕組みが用意されている。それが `Presentation.CustomDocumentProperties`(文書プロパティ) だ。
今回は、この `CustomDocumentProperties` の癖の強い仕様を完全に調教し、「型安全かつ堅牢にメタデータをハンドリングするカスタムプロパティ操作ライブラリ」の設計と実装を伝授する。
—
1. なぜ `CustomDocumentProperties` なのか? なぜ生のままだと地獄なのか?
`CustomDocumentProperties` は、Officeドキュメントのメタデータ(ファイルプロパティの「ユーザー設定」タブに相当するもの)をプログラムから操作するためのコレクションだ。
これを採用する理由は明確である。
- ファイルと運命を共にする: スライド上のシェイプのように、うっかり削除されるリスクがない。別名保存(`SaveAs`)してもメタデータは継承される。
- データベース連携の親和性: キー・バリュー形式で値を保持でき、RDBのレコードと1対1でマッピングしやすい。
しかし、生の `CustomDocumentProperties` をそのまま触るのは地雷原を裸足で歩くようなものだ。
地雷その1:存在しないキーへのアクセスで容赦なく走る実行時エラー
辞書(Dictionary)オブジェクトのように `Properties(“Key”)` と指定して、キーがなければ自動で新規作成…とはいかない。存在しないキーを取得しようとすると、容赦なく 実行時エラー `-2147467259 (80004005)` が飛んでくる。必ず「存在確認」のボイラープレートを書くか、エラーハンドリングを挟む必要がある。
地雷その2:型アサーションの欠如とVBAの暗黙の型の罠
`CustomDocumentProperty.Value` は `Variant` 型だ。データを書き込む際、VBAは勝手に型を推論するが、これが曲者。特に「日付型(Date)」や「真偽値(Boolean)」を書き込んで再取得した際、Variantの型が意図しない形に化けたり、Excel/Access等へエクスポートする際に型不整合を起こす原因になる。
この問題を解決するため、「型安全なgetter/setterを持つクラス(あるいは構造化されたラッパーモジュール)」を構築する。
—
2. 設計思想:堅牢なメタデータ操作ライブラリのアーキテクチャ
今回は、VBAのクラスモジュール(例: `clsMetadataManager`)として設計する。
外部からは「どの型のデータを保存・取得したいか」を意識させつつ、内部で `CustomDocumentProperties` への安全な橋渡しを行う。
対応すべき4つのデータ型
1. 文字列 (String)
2. 長整数 (Long / Integer)
3. 日付・時刻 (Date)
4. 真偽値 (Boolean)
これらを明確に区別し、不正な型が混入した場合は即座に検出・例外スローする設計にする。
—
3. プロダクションコード実装
以下のコードを、VBAエディタで新規クラスモジュールを作成し、名前を `clsMetadataManager` としてそのまま貼り付けてほしい。
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 ‘True
END
Attribute VB_Name = “clsMetadataManager”
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
‘ ==============================================================================
‘ クラス名: clsMetadataManager
‘ 概要: Presentation.CustomDocumentPropertiesを型安全に操作するラッパーライブラリ
‘ 開発者: 業務自動化アーキテクト
‘ ==============================================================================
Option Explicit
Private m_TargetPresentation As Presentation
‘ 初期化時に対象のプレゼンテーションをバインド
Public Sub Initialize(ByVal targetPres As Presentation)
If targetPres Is Nothing Then
Err.Raise 91, “clsMetadataManager”, “対象のプレゼンテーションが指定されていません(Nothingです)。”
End If
Set m_TargetPresentation = targetPres
End Sub
‘ ==============================================================================
‘ 【Setter群】各型に応じた安全な書き込み
‘ ==============================================================================
Public Sub SetString(ByVal key As String, ByVal value As String)
Call SetProperty(key, value, msoPropertyTypeString)
End Sub
Public Sub SetLong(ByVal key As String, ByVal value As Long)
Call SetProperty(key, value, msoPropertyTypeNumber)
End Sub
Public Sub SetDate(ByVal key As String, ByVal value As Date)
‘ VBAのDate型は内部的にDoubleのため、確実性を期すため数値型としてパースするかDateとして渡す
Call SetProperty(key, value, msoPropertyTypeDate)
End Sub
Public Sub SetBoolean(ByVal key As String, ByVal value As Boolean)
Call SetProperty(key, value, msoPropertyTypeBoolean)
End Sub
‘ ==============================================================================
‘ 【Getter群】各型に応じた安全な読み込み(デフォルト値フォールバック付き)
‘ ==============================================================================
Public Function GetString(ByVal key As String, Optional ByVal defaultValue As String = “”) As String
Dim val As Variant
val = GetProperty(key)
If IsEmpty(val) Then
GetString = defaultValue
Else
GetString = CStr(val)
End If
End Function
Public Function GetLong(ByVal key As String, Optional ByVal defaultValue As Long = 0) As Long
Dim val As Variant
val = GetProperty(key)
If IsEmpty(val) Or Not IsNumeric(val) Then
GetLong = defaultValue
Else
GetLong = CLng(val)
End If
End Function
Public Function GetDate(ByVal key As String, Optional ByVal defaultValue As Date) As Date
Ifшчы IsEmpty(defaultValue) Then defaultValue = CDate(0)
Dim val As Variant
val = GetProperty(key)
If IsEmpty(val) Or Not IsDate(val) Then
GetDate = defaultValue
Else
GetDate = CDate(val)
End If
End Function
Public Function GetBoolean(ByVal key As String, Optional ByVal defaultValue As Boolean = False) As Boolean
Dim val As Variant
val = GetProperty(key)
If IsEmpty(val) Then
GetBoolean = defaultValue
Else
‘ 厳密なBoolean評価
If VarType(val) = vbBoolean Then
GetBoolean = CBool(val)
Else
‘ 文字列等で保存されてしまっている場合のフォールバック
Select Case LCase(CStr(val))
Case “true”, “1”, “yes”: GetBoolean = True
Case Else: GetBoolean = False
End Select
End If
End If
End Function
‘ ==============================================================================
‘ 【削除メソッド】
‘ ==============================================================================
Public Function DeleteProperty(ByVal key As String) As Boolean
On Error GoTo ErrorHandler
Dim props As Office.DocumentProperties
Set props = m_TargetPresentation.CustomDocumentProperties
Dim prop As Office.DocumentProperty
For Each prop In props
If StrComp(prop.Name, key, vbTextCompare) = 0 Then
prop.Delete
DeleteProperty = True
Exit Function
End If
Next prop
DeleteProperty = False
Exit Function
ErrorHandler:
DeleteProperty = False
End Function
‘ ==============================================================================
‘ 【内部コアロジック】プロパティの低レベル操作
‘ ==============================================================================
Private Sub SetProperty(ByVal key As String, ByVal value As Variant, ByVal propType As MsoDocProperties)
Dim props As Office.DocumentProperties
Set props = m_TargetPresentation.CustomDocumentProperties
‘ 既存存在チェックと更新
On Error Resume Next
props(key).Value = value
If Err.Number = 0 Then
On Error GoTo 0
Exit Sub
End If
On Error GoTo 0
‘ 存在しない場合は新規追加
‘ ※注意: 型によってはAddメソッドでエラーになることがあるため、厳密にハンドリング
props.Add Name:=key, LinkToContent:=False, Type:=propType, Value:=value
End Sub
Private Function GetProperty(ByVal key As String) As Variant
Dim props As Office.DocumentProperties
Set props = m_TargetPresentation.CustomDocumentProperties
On Error GoTo NotFound
GetProperty = props(key).Value
Exit Function
NotFound:
GetProperty = Empty
End Function
—
4. 現場で即座に使える実践サンプルコード
標準モジュールを作成し、上記で作った `clsMetadataManager` をどのように呼び出して業務効率化に活かすのか、実践的なコードを見てみよう。
Sub Sample_MetadataWorkflow()
Dim meta As clsMetadataManager
Set meta = New clsMetadataManager
‘ アクティブなプレゼンテーションをバインド
meta.Initialize ActivePresentation
‘ 1. データの書き込み(社内DB連携を想定した多様なデータ型)
meta.SetString “ProjectCode”, “PRJ-202X-99”
meta.SetLong “RevisionNumber”, 3
meta.SetDate “ApprovalDate”, Now
meta.SetBoolean “IsApproved”, True
MsgBox “メタデータを型安全に書き込みました。”, vbInformation, “保存完了”
‘ 2. データの読み込み(デフォルト値フォールバック付き)
Dim pCode As String
Dim rev As Long
Dim appDate As Date
Dim isApp As Boolean
pCode = meta.GetString(“ProjectCode”, “UNKNOWN”)
rev = meta.GetLong(“RevisionNumber”, 0)
appDate = meta.GetDate(“ApprovalDate”, Date)
isApp = meta.GetBoolean(“IsApproved”, False)
‘ デバッグ出力(イミディエイトウィンドウで確認)
Debug.Print “— 読み込み結果 —”
Debug.Print “プロジェクトコード: ” & pCode
Debug.Print “リビジョン: ” & rev
Debug.Print “承認日時: ” & Format(appDate, “yyyy/mm/dd hh:nn:ss”)
Debug.Print “承認ステータス: ” & isApp
‘ 3. メタデータの削除テスト
‘ meta.DeleteProperty “IsApproved”
Set meta = Nothing
End Sub
—
5. チーフアーキテクトからの実践的アドバイス・注意点
1. 参照設定の確認(コンパイルエラーを防ぐために)
このコードでは `Office.DocumentProperties` といった型を使用している。VBAエディタのメニューから [ツール] > [参照設定] を開き、「Microsoft Office x.x Object Library」 にチェックが入っていることを確認してほしい。(※入っていなくても実行できるが、型の明示によるインテリセンスの恩恵を受けるために参照設定を推奨する)
2. ファイルサーバー経由の排他制御
複数人が同時にアクセスするファイルサーバー上のPowerPointに対し、VBAからメタデータを書き込んで即座に `Save` を叩く場合、「書き込み競合(Access Denied)」が発生するリスクがある。
データベース連携バッチなどで自動処理する場合は、ファイルをローカルのテンポラリ領域にコピーしてから処理を完結させ、最後に上書き保存する設計を推奨する。
3. 他システム(C#.NETやPythonなど)からの連携
この `CustomDocumentProperties` の素晴らしい点は、VBAだけでなく、外部のC# (OpenXML SDK / COM Interop) やPython (python-pptx 等では一部制限ありだがCOM経由なら) からも同じキー名で読み書きできる点にある。
「VBAでメタデータを仕込み、夜間のバッチサーバー(C#)でそのメタデータを読み取ってSQL Serverへ同期する」といったモダンなアーキテクチャの基盤として、このクラス設計はそのまま流用できる。
—
総括
「動けばいいや」で書いた場当たり的なVBAコードは、半年後の自分、あるいは後任のエンジニアを苦しめる呪いとなる。
メタデータ管理という「システムの土台」こそ、厳格な型安全性を担保し、堅牢に作らなければならない。
今回の `clsMetadataManager` をあなたの開発プロジェクトに組み込み、エラーフリーでスマートな自動化ライフを実現してほしい。
