破壊的変更を避け、純粋な「抽出」を設計せよ:スライド・フィルタリングのアーキテクチャ
業務自動化の現場において、最もリスクが高いのは「既存のデータを書き換える」操作だ。
特に、社内向けの機密情報(スピーカーノートに書かれた非公開コメントや、検討中のメタデータ)が含まれたプレゼンテーションから、外部公開用の「クリーンなスライド」を切り出す作業。これを「元のファイルから不要なスライドを消していく」というアプローチで実装しようとする者がいる。
それは、エンジニアとして致命的な設計ミスだ。
スライドを削除していくループ処理は、インデックスのズレという古典的なバグを誘発するだけでなく、万が一の実行エラー時に「元データが破損した状態」で残るリスクを孕んでいる。プロフェッショナルが構築すべきは、元のソースを一切汚染せず、条件に合致するものだけを「新しい器」へと転送する「抽出型(Extraction-based)アーキテクチャ」である。
本稿では、スピーカーノート内の制御タグ(例:`[Skip]`)を検知し、安全かつ高速にクリーンなプレゼンテーションを生成する、プロダクションレベルのVBA実装を伝授する。
1. 設計思想:Mutation(変異)ではなく Extraction(抽出)
中級者が陥る罠は、`Slides(i).Delete` を使った破壊的処理だ。これを避けるべき理由は3つある。
1. インデックスの不整合: ループ中にオブジェクトを削除すると、残されたオブジェクトのインデックスが動的に変化し、スライドの読み飛ばしが発生する。
2. データの不可逆性: 処理中にエラーが発生した場合、どのスライドが削除され、どのスライドが残ったのかの整合性を保つことが困難になる。
3. メタデータの残留: スライドを消しても、スライドマスターやノートの残骸がファイル内に潜伏し続けるリスクがある。
我々が目指すべきは、「新しいプレゼンテーション・インスタンスを生成し、合格したスライドのみをコピーして集める」という、不変性(Immutability)を重視した設計だ。
2. 実装:高効率・堅牢なフィルタリングエンジン
以下のコードは、単なるコピペ用スクリプトではない。エラーハンドリング、パフォーマンス(クリップボード操作の最小化)、および保守性を考慮した、実務投入可能な設計となっている。
Option Explicit
''' <summary>
''' スピーカーノートに特定のタグがあるスライドを除外した、
''' クリーンなプレゼンテーションを新規作成する。
''' </summary>
Public Sub CreateCleanPresentation()
' --- 設定領域 ---
Const SKIP_TAG As String = "[Skip]"
Dim sourcePres As Presentation
Dim targetPres As Presentation
Dim slideIdx As Long
Dim validIndices() As Long
Dim validCount As Long
Dim savePath As String
' エラーハンドリングの開始
On Error GoTo ErrorHandler
' 1. ソースプレゼンテーションの取得
Set sourcePres = ActivePresentation
If sourcePres Is Nothing Then
MsgBox "ソースとなるプレゼンテーションが開かれていません。", vbCritical
Exit Sub
End If
' 2. フィルタリングロジック(インデックスの収集)
' スライドを直接コピーするのではなく、まず「残すべきインデックス」をメモリ上で特定する。
' これにより、クリップボードへのアクセス回数を最小限に抑える。
validCount = 0
For slideIdx = 1 To sourcePres.Slides.Count
If Not IsSlideSkipped(sourcePres.Slides(slideIdx), SKIP_TAG) Then
validCount = validCount + 1
ReDim Preserve validIndices(1 To validCount)
validIndices(validCount) = slideIdx
End If
Next slideIdx
If validCount = 0 Then
MsgBox "抽出対象のスライドが見つかりませんでした。", vbExclamation
Exit Sub
End If
' 3. 新規プレゼンテーションの生成とスライドの複製
' 新しいプレゼンテーションを作成
Set targetPres = Presentations.Add(WithWindow:=msoTrue)
' 効率化の鍵:スライドを一つずつコピーするのではなく、
' スライド範囲(SlideRange)として一括コピーを試みる設計が理想だが、
' PowerPoint VBAの制約上、ここでは確実に型を維持するためにループ内でコピーを行う。
' ただし、インデックスは常に「ソース」を参照しているため安全。
For slideIdx = 1 To validCount
sourcePres.Slides(validIndices(slideIdx)).Copy
targetPres.Slides.Paste
Next slideIdx
' 4. 保存処理
savePath = Replace(sourcePres.FullName, ".pptx", "_Clean.pptx")
' 同名ファイルが存在する場合の対策
If Dir(savePath) <> "" Then
savePath = Replace(savePath, ".pptx", "_" & Format(Now, "yyyymmdd_hhnnss") & ".pptx")
End If
targetPres.SaveAs savePath
MsgBox "クリーンなプレゼンテーションを作成しました:" & vbCrLf & savePath, vbInformation, "完了"
ExitSub:
' オブジェクトの解放(メモリ管理)
Set targetPres = Nothing
Set sourcePres = Nothing
Exit Sub
ErrorHandler:
MsgBox "致命的なエラーが発生しました。" & vbCrLf & _
"Error No: " & Err.Number & vbCrLf & _
"Description: " & Err.Description, vbCritical, "実行エラー"
Resume ExitSub
End Sub
''' <summary>
''' スライドのスピーカーノートに特定のタグが含まれているか判定する。
''' </summary>
''' <param name="targetSlide">判定対象のスライド</param>
''' <param name="tag">検索するタグ(例: [Skip])</param>
''' <returns>スキップすべき場合はTrue</returns>
Private Function IsSlideSkipped(ByRef targetSlide As Slide, ByVal tag As String) As Boolean
Dim noteText As String
' スピーカーノートが存在しない場合のエラーを回避
On Error Resume Next
noteText = targetSlide.NotesPage.Shapes.Placeholders(2).TextFrame.TextRange.Text
On Error GoTo 0
' タグが含まれているか判定 (大文字小文字を区別しない)
If InStr(1, noteText, tag, vbTextCompare) > 0 Then
IsSlideSkipped = True
Else
IsSlideSkipped = False
End If
End Function
3. アーキテクトの視点:なぜこのコードは「強い」のか
① メモリ効率とインデックスの分離
`validIndices()` という動的配列を使用している点が肝だ。スライドの判定(ロジック)と、スライドの操作(アクション)を完全に分離している。これにより、スライドを削除したことによる「インデックスのズレ」という低レベルなバグを構造的に排除している。
② `IsSlideSkipped` の堅牢性
スピーカーノートへのアクセスは、実は非常に脆い。ノートが存在しないスライドや、プレースホルダーが壊れているスライドに対してアクセスすると、VBAは即座にランタイムエラーを吐く。`On Error Resume Next` を局所的に使い、ノートのテキスト取得を「防衛的」に行うことで、処理の継続性を担保している。
③ クリップボード・オーバーヘッドへの配慮
スライドのコピー&ペーストは、OSのクリップボードを介するため、非常に重い操作だ。本来であれば `SlideRange.Copy` で一括コピーするのが最速だが、スライドの選択順序によってはフォーマットが崩れるリスクがある。本コードでは、あえて「判定済みのインデックスのみをループで回す」ことで、不必要なコピー操作を最小限に抑えつつ、確実に目的のスライドだけを転送している。
4. 実務における注意点:さらなる高みへ
このツールをプロダクション環境(実務の自動化ライン)に組み込む際は、以下の点に留意せよ。
- スライドマスターの継承: `Slides.Paste` を使用した場合、コピー先にはソースのスライドマスターが持ち込まれる。これにより、見た目は完璧に維持されるが、ファイルサイズが肥大化することがある。配布用として極限まで軽量化したい場合は、マスターを再定義する処理を追加検討せよ。
- ファイルI/Oの競合: `SaveAs` を実行する際、同名のファイルが既に開かれているとエラーになる。本コードではタイムスタンプを付与する回避策を入れているが、システム連携する場合は、ファイルロックのチェックを先行して行うべきだ。
- データベース連携への拡張: もし「どのスライドがスキップされたか」のログをDBに記録したい場合は、`IsSlideSkipped` が `True` を返した瞬間に、スライド番号とタイムスタンプをログテーブルへ `INSERT` する処理を差し込め。それが「監査証跡(Audit Trail)」として機能する。
結論
自動化エンジニアの仕事は、単に「動くもの」を作ることではない。「壊れないもの」を、「予測可能な方法で」作ることだ。
今回提示した「抽出型」の設計思想をマスターすれば、君の書くコードは単なるスクリプトから、信頼に値する「ソフトウェア」へと昇華するだろう。
