【実務・中級編】【初心者向け】特定の文字列を含むページを検索し、一括で別ファイルへエクスポートする抽出ツール – Visio VBA解析バイブル

スポンサーリンク
スポンサーリンク

【極限の知見】Visio VBAによるページ抽出ツールの設計論:単なる「検索」を「プロダクション級の自動化」へ昇華させる

エンジニア諸君、現場で「これ、自動化しといて」と頼まれたとき、君たちはどう動く?

「ページ名をループで回して、`If`文で文字列を判定し、新しいドキュメントにコピーする」……。もし、そんな書き方でコードを書き始めたのなら、今すぐその手を止めてほしい。それは「自動化」ではない。単なる「脆弱なスクリプト」だ。

実務における自動化ツールには、「例外への耐性」「リソースの管理」「保守性の高い設計」という3つの柱が不可欠である。特にVisioのようなグラフィカルなオブジェクトモデルを扱う場合、メモリ上のオブジェクトライフサイクルや、ページ間の接続関係(Connector)の断絶といった、初心者が見落としがちな「落とし穴」が至る所に潜んでいる。

本記事では、特定の文字列を含むページを検出し、別ファイルとして一括エクスポートするツールを題材に、「動くだけのコード」を「業務で耐えうるプロダクションコード」へ昇華させるための設計思想を伝授する。

1. 凡ミスが招く「壊れやすい設計」の正体

多くの開発者が陥る罠は、「検索ロジック」と「エクスポートロジック」を密結合させてしまうことだ。

例えば、以下のような書き方は避けるべきだ。

  • `On Error Resume Next` でエラーを握りつぶす(どこで失敗したか分からなくなる)。
  • シェイプのテキスト検索を、ページ名の検索と同じレベルの単純なループで行う(計算量の増大と、階層構造の無視)。
  • 新しいファイルを作成する際、元のページのレイヤー設定やページ設定(サイズ・向き)を考慮しない。

これでは、抽出されたファイルが「見た目も設定もバラバラなゴミの集まり」になり、結局、人間が手直しする羽目になる。これでは自動化の意味がない。

2. アーキテクチャの設計:関心の分離

堅牢なツールを作るためには、機能を以下の3つのフェーズに分離(Decoupling)して設計する。

1. Search Engine (検索エンジン): 条件に合致するページオブジェクトを特定する。
2. Collector (コレクター): 特定されたページをメモリ上に一時保持、あるいはリスト化する。
3. Exporter (エクスポートエンジン): 新規ドキュメントを生成し、ページ設定を継承しながらデータを書き出す。

この設計により、「検索条件を『ページ名』から『シェイプ内の特定のプロパティ』に変えたい」といった仕様変更に対しても、検索エンジンの一部を書き換えるだけで対応が可能になる。

3. 実装:プロダクション・グレードの抽出ツール

以下に、実務でそのまま使えるレベルのコードを示す。単なるコピペで終わらせず、コメントに記した「なぜこう書くのか」という意図を読み取ってほしい。

Option Explicit

' ==============================================================================
' Module: PageExtractor
' Description: 特定の文字列を含むページを抽出し、新ファイルへエクスポートする
' Author: Chief Architect
' ==============================================================================

Sub ExecutePageExtraction()
    Dim targetKeyword As String
    Dim destinationPath As String
    Dim sourceDoc As Visio.Document
    Dim matchedPages As Collection
    Dim i As Integer
    
    ' --- 1. 初期設定と入力バリデーション ---
    targetKeyword = InputBox("検索するキーワードを入力してください", "抽出条件設定")
    If targetKeyword = "" Then Exit Sub ' キャンセル時は即終了
    
    Set sourceDoc = ActiveDocument
    Set matchedPages = New Collection
    
    ' エラーハンドリングの開始
    On Error GoTo ErrorHandler
    
    ' --- 2. 検索フェーズ (Search Engine) ---
    ' ページ名だけでなく、シェイプ内のテキストも走査する
    Debug.Print "Searching for: " & targetKeyword
    Call FindMatchingPages(sourceDoc, targetKeyword, matchedPages)
    
    If matchedPages.Count = 0 Then
        MsgBox "該当するページは見つかりませんでした。", vbInformation
        Exit Sub
    End If
    
    ' --- 3. エクスポートフェーズ (Exporter) ---
    ' 保存先のパスを生成(実行ファイルと同階層を想定)
    destinationPath = sourceDoc.Path & "\Extracted_" & Format(Now, "yyyymmdd_hhnnss") & ".vsdx"
    
    MsgBox matchedPages.Count & " 件のページが見つかりました。エクスポートを開始します。"
    
    Call ExportPagesToNewDoc(sourceDoc, matchedPages, destinationPath)
    
    MsgBox "完了しました!" & vbCrLf & "保存先: " & destinationPath, vbInformation
    
CleanUp:
    ' オブジェクトの解放(メモリリーク防止)
    Set matchedPages = Nothing
    Set sourceDoc = Nothing
    Exit Sub

ErrorHandler:
    MsgBox "致命的なエラーが発生しました。" & vbCrLf & _
           "Error No: " & Err.Number & vbCrLf & _
           "Description: " & Err.Description, vbCritical
    Resume CleanUp
End Sub

''' <summary>
''' 指定されたキーワードを含むページを特定し、コレクションに格納する
''' </summary>
Private Sub FindMatchingPages(ByRef doc As Visio.Document, ByVal keyword As String, ByRef pageCol As Collection)
    Dim pg As Visio.Page
    Dim shp As Visio.Shape
    
    For Each pg In doc.Pages
        ' 条件A: ページ名にキーワードが含まれるか
        If InStr(1, pg.Name, keyword, vbTextCompare) > 0 Then
            pageCol.Add pg
        Else
            ' 条件B: ページ内のシェイプのテキストにキーワードが含まれるか
            ' 全シェイプを走査。階層構造を考慮し、再帰ではなく標準的なループで実装(パフォーマンス重視)
            For Each shp In pg.Shapes
                If shp.Text != "" Then
                    If InStr(1, shp.Text, keyword, vbTextCompare) > 0 Then
                        pageCol.Add pg
                        Exit For ' 1つ見つかればそのページは確定
                    End If
                End If
            Next shp
        End If
    Next pg
End Sub

''' <summary>
''' 特定されたページを新しいドキュメントへコピー&ペーストする
''' </summary>
Private Sub ExportPagesToNewDoc(ByRef srcDoc As Visio.Document, ByRef pageCol As Collection, ByVal savePath As String)
    Dim newDoc As Visio.Document
    Dim srcPg As Visio.Page
    Dim newPg As Visio.Page
    Dim i As Integer
    
    ' 新規ドキュメントの作成(テンプレートなしの空ドキュメント)
    ' ※実務では、会社の標準テンプレートを指定して作成するのが定石
    Set newDoc = Documents.Add("")
    
    For i = 1 To pageCol.Count
        Set srcPg = pageCol(i)
        
        ' 1. 新しいページを作成
        Set newPg = newDoc.Pages.Add
        newPg.Name = srcPg.Name
        
        ' 2. ページ設定の継承(ここがプロの仕事)
        ' サイズ、向き、グリッド設定などを同期させる
        newPg.PageSheet.CellsU("PageWidth").FormulaU = srcPg.PageSheet.CellsU("PageWidth").FormulaU
        newPg.PageSheet.CellsU("PageHeight").FormulaU = srcPg.PageSheet.CellsU("PageHeight").FormulaU
        newPg.PageSheet.CellsU("PageOrientation").FormulaU = srcPg.PageSheet.CellsU("PageOrientation").FormulaU
        
        ' 3. シェイプのコピー&ペースト
        ' Page.Shapes.SelectAll は不安定なため、各シェイプをループしてコピーする手法が最も堅牢
        ' ただし、大量のシェイプがある場合は Selection.Copy を検討する
        srcPg.Shapes.SelectAll
        ActiveWindow.Selection.Copy
        newPg.Paste
        
        ' コピー後の選択解除
        newPg.Shapes.SelectAll ' 一旦全選択
        ActiveWindow.Selection.Deselect ' 選択解除(Visioの挙動により調整が必要な場合あり)
    Next i
    
    ' 最初の空ページ(デフォルトで作られるページ)を削除
    If newDoc.Pages.Count > pageCol.Count Then
        newDoc.Pages(1).Delete
    End If
    
    ' 保存
    newDoc.SaveAs savePath
    newDoc.Close
End Sub

4. チーフアーキテクトによる「技術的解説」

このコードがなぜ「ただのスクリプト」ではないのか。その設計意図を解説する。

① 計算量と検索の効率化

`FindMatchingPages` 内では、ページ名で見つかった瞬間に `Exit For` を実行している。これは、1つのページ内に大量のシェイプが存在する場合、無駄なスキャンを避けるためだ。また、`InStr` 関数に `vbTextCompare` を指定することで、大文字・小文字の差異を無視した柔軟な検索を実現している。

② ページ設定(PageSheet)の継承

ここが最も重要なポイントだ。単にシェイプをコピーするだけでは、元の図面が「A3横」であっても、新しいファイルでは「A4縦」になってしまう。
`newPg.PageSheet.CellsU(“PageWidth”).FormulaU = …`
この一行によって、図面の物理的コンテキストを維持している。これこそが、ユーザーが「使える」と感じるツールの差である。

③ リソース管理とエラーハンドリング

VBAはオブジェクトの解放を怠ると、Visioのプロセスがメモリに残り続け、アプリケーションが不安定になる原因となる。`CleanUp` ラベルを用いた構造化エラーハンドリングにより、エラーが発生した際でも必ずオブジェクトを `Nothing` にして終了するように設計している。

④ 接続関係(Connectors)に関する警告

本コードはシェイプをコピーしているが、Visioの特性上、「別ページにあるシェイプへのコネクタ」は、コピー先では切断される。もし、ページをまたいだ接続関係を維持したままエクスポートする必要があるなら、全ページを一度に新しいドキュメントへコピーする、より高度なロジックが必要になる。設計要件を確認する際は、必ずこの点に触れておくべきだ。

まとめ:プロフェッショナルとしての視点

自動化ツールを開発する際、君たちは「コードを書く人」ではなく「業務プロセスを再設計する人」でなければならない。

  • 「何を探すか」だけでなく「どのように維持するか」を考えろ。
  • 「成功ルート」だけでなく「失敗ルート」を設計しろ。
  • 「動く」だけでなく「保守できる」コードを書け。

この視点を持つだけで、君たちの書くコードの価値は、単なる作業補助から、企業の資産へと変わるはずだ。健闘を祈る。

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