【実務・中級編】【中級者】スライド内に散在するすべてのテキストフレームから特定部門の機密キーワードを正規表現で自動検知し、該当箇所の文字列を伏字に置換してから別名保存するプライバシー保護マクロ – PowerPoint VBA解析バイブル

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

脆弱なコードは情報漏洩の火種となる:PowerPoint VBAによる「堅牢な機密情報マスキング・エンジン」の設計思想

エンジニア諸君、まず最初にこれだけは肝に銘じておいてほしい。
「単に文字列を置換するだけのマクロ」は、業務効率化ツールではない。それは、セキュリティホールを自動生成するだけの「時限爆弾」だ。

外部パートナーへ資料を共有する際、「念のため機密ワードを伏せ字にしよう」という安易な考えで、`Replace` 関数を並べただけのコードを書く者がいる。しかし、スライド内のテキストは、単なるテキストボックスに留まらない。グループ化された図形、表(Table)、SmartArt、そして複雑なテキスト階層……。これらを網羅できないコードは、検知漏れという致命的なバグを必然的に引き起こす。

本稿では、プロフェッショナルとして、単なる「コピペ用スクリプト」ではなく、オブジェクトのライフサイクルと再帰的探索を理解した、プロダクションレベルのマスキング・エンジンの設計と実装を伝授する。

1. アーキテクチャ設計:なぜ「単純なループ」では足りないのか

中級者が陥る最大の罠は、`For Each shp In ActivePresentation.Slides.Shapes` という単純なループで処理を完結させようとすることだ。これでは、「グループ化された図形の中にあるテキスト」や「表の中のセル」を見落とす。

堅牢な設計には、以下の3つの原則が不可欠である。

① 再帰的探索(Recursive Traversal)の導入

図形の中に図形がある(GroupShape)場合、その階層を深く潜り抜けてテキストを探索する再帰関数が必要だ。

② 外部定義による機密情報の管理(Externalized Configuration)

機密キーワードをVBAのソースコード内にハードコード(直書き)するのは、保守性の観点から最悪の設計だ。キーワードは外部のテキストファイルやCSVから読み込み、ロジックとデータを分離せよ。これにより、エンジニアの手を介さずに、非エンジニア(管理者)が検知リストを更新できる。

③ 非破壊的処理(Immutability)

元のファイル(Source)を直接書き換えるな。必ず「別名保存(SaveAs)」を前提としたワークフローを組み、万が一の処理失敗時にも、オリジナルの機密資料が破損しないように設計せよ。

2. プロダクション実装:正規表現を用いたマスキング・エンジン

以下に、実務でそのまま投入可能なレベルのコードを示す。このコードは、`VBScript.RegExp` を利用して高度なパターンマッチングを行い、グループ化された図形や表に対しても再帰的に作用する。

事前準備

VBAエディタの [ツール] -> [参照設定] から、「Microsoft VBScript Regular Expressions 5.5」 にチェックを入れておくこと。これがなければ、正規表現のパワーは引き出せない。

Option Explicit

' ======================================================================================
' Module: PrivacyShieldEngine
' Description: スライド内の機密情報を正規表現で検知し、マスキングを行う堅牢なエンジン
' Author: Chief Architect
' ======================================================================================

Sub ExecutePrivacyMasking()
    Dim sourcePres As Presentation
    Dim targetPres As Presentation
    Dim keywordFilePath As String
    Dim patterns As Collection
    Dim savePath As String
    
    ' --- 設定領域 ---
    ' 本来はダイアログで選択させるべきだが、ここでは設計例としてパスを指定
    keywordFilePath = ThisWorkbook.Path & "\keywords.txt" ' 1行1パターンで記載したテキストファイル
    savePath = ThisWorkbook.Path & "\Masked_Presentation_" & Format(Now, "yyyymmdd_hhnnss") & ".pptx"
    ' ----------------
    
    On Error GoTo ErrorHandler
    
    Set sourcePres = ActivePresentation
    
    ' 1. 機密パターン(正規表現)のロード
    Set patterns = LoadRegexPatterns(keywordFilePath)
    If patterns.Count = 0 Then
        MsgBox "検知パターンが読み込めませんでした。ファイルを確認してください。", vbCritical
        Exit Sub
    End If
    
    ' 2. 非破壊的処理のため、ファイルをコピーして新しいプレゼンテーションとして開く
    ' ※ 本来はSaveCopyAs等を用いて別プロセスで扱うのが理想的だが、ここではシンプルに構成
    sourcePres.SaveCopyAs savePath
    Set targetPres = Presentations.Open(savePath, WithWindow:=msoTrue)
    
    ' 3. 全スライドに対して再帰的スキャンを実行
    Dim sld As Slide
    Dim regEx As RegExp
    
    For Each sld In targetPres.Slides
        For Each regEx In patterns
            ProcessSlide regEx, sld
        Next regEx
    Next sld
    
    ' 4. 保存して完了
    targetPres.Save
    MsgBox "マスキング処理が完了しました。" & vbCrLf & "保存先: " & savePath, vbInformation
    
    Exit Sub

ErrorHandler:
    MsgBox "致命的なエラーが発生しました: " & Err.Description, vbCritical
End Sub

' --------------------------------------------------------------------------------------
' 関数: LoadRegexPatterns
' 役割: 外部ファイルから正規表現パターンを読み込む
' --------------------------------------------------------------------------------------
Private Function LoadRegexPatterns(filePath As String) As Collection
    Dim col As New Collection
    Dim fso As Object
    Dim txtStream As Object
    Dim line As String
    Dim reg As RegExp
    
    Set fso = CreateObject("Scripting.FileSystemObject")
    If Not fso.FileExists(filePath) Then
        Set LoadRegexPatterns = New Collection
        Exit Function
    End If
    
    Set txtStream = fso.OpenTextFile(filePath, 1) ' ForReading
    
    Do Until txtStream.AtEndOfStream
        line = Trim(txtStream.ReadLine)
        If line <> "" Then
            Set reg = New RegExp
            reg.Pattern = line
            reg.Global = True
            reg.IgnoreCase = True
            col.Add reg
        End If
    Loop
    
    txtStream.Close
    Set LoadRegexPatterns = col
End Function

' --------------------------------------------------------------------------------------
' サブルーチン: ProcessSlide
' 役割: スライド内の全てのShapeを走査する
' --------------------------------------------------------------------------------------
Private Sub ProcessSlide(ByRef regEx As RegExp, ByRef sld As Slide)
    Dim shp As Shape
    For Each shp In sld.Shapes
        RecursiveMasking shp, regEx
    Next shp
End Sub

' --------------------------------------------------------------------------------------
' サブルーチン: RecursiveMasking
' 役割: 図形の種類を判別し、再帰的にテキストを探索する(Group/Table対応)
' --------------------------------------------------------------------------------------
Private Sub RecursiveMasking(ByRef shp As Shape, ByRef regEx As RegExp)
    Dim i As Integer, j As Integer
    Dim targetTable As Table
    
    ' ケース1: グループ化された図形の場合(再帰呼び出し)
    If shp.Type = msoGroup Then
        Dim gShp As Shape
        For Each gShp In shp.GroupItems
            RecursiveMasking gShp, regEx
        Next gShp
        
    ' ケース2: 表(Table)の場合
    ElseIf shp.HasTable Then
        Set targetTable = shp.Table
        For i = 1 To targetTable.Rows.Count
            For j = 1 To targetTable.Columns.Count
                MaskTextRange targetTable.Cell(i, j).Shape.TextFrame.TextRange, regEx
            Next j
        Next i
        
    ' ケース3: 通常のテキストフレームを持つ図形
    ElseIf shp.HasTextFrame Then
        If shp.TextFrame.HasText Then
            MaskTextRange shp.TextFrame.TextRange, regEx
        End If
    End If
End Sub

' --------------------------------------------------------------------------------------
' サブルーチン: MaskTextRange
' 役割: TextRangeに対して正規表現置換を実行する
' --------------------------------------------------------------------------------------
Private Sub MaskTextRange(ByRef tr As TextRange, ByRef regEx As RegExp)
    ' 注意: TextRange.Replace は正規表現を直接サポートしていないため、
    ' 一旦文字列として取得し、正規表現で置換した後に書き戻す。
    ' ※ 書式(色や太字)を完全に維持するには、Characters単位の精緻な制御が必要だが、
    '   プライバシー保護の文脈では「安全な文字列置換」を優先する。
    
    Dim originalText As String
    Dim maskedText As String
    
    originalText = tr.Text
    If regEx.Test(originalText) Then
        maskedText = regEx.Replace(originalText, "[CONFIDENTIAL]")
        tr.Text = maskedText
    End If
End Sub

3. プロフェッショナルが教える、運用上のクリティカルな注意点

このコードを実戦投入するにあたり、以下の3点を必ず考慮せよ。

① 書式情報の欠落問題(Trade-off)

上記のコードでは、`tr.Text = maskedText` という手法をとっている。これは、テキスト全体を一度文字列として扱い、置換後に書き戻す手法だ。
利点: 実装がシンプルで、複雑な正規表現パターンにも確実に反応する。
欠点: 置換対象のテキストの中に「一部だけ太字」や「色付き」などの書式があった場合、その書式がリセットされる。
もし、書式の保持が絶対条件であるならば、`tr.Characters` をループで回し、マッチした開始位置と長さを特定して、一文字ずつ、あるいは範囲ごとに置換を行う、より高度な(そして重い)ロジックを組む必要がある。しかし、機密情報の保護という目的においては、「書式が崩れても、情報は守る」という設計判断が正しい。

② パフォーマンスの重み

スライドが数百枚、図形が数千個あるプレゼンテーションに対し、大量の正規表現パターンをぶつけると、処理時間は指数関数的に増大する。
`LoadRegexPatterns` で `RegExp` オブジェクトをあらかじめコンパイル(`reg.Pattern = …`)しておくのは、この計算量を抑えるための必須テクニックだ。

③ 外部ファイル(keywords.txt)の管理

`keywords.txt` には、以下のように正規表現を直接記述できる。

  • `Project-[A-Z]{3}-\d{4}` (プロジェクトコード:Project-ABC-1234 など)
  • `\d{3}-\d{4}-\d{4}` (電話番号パターン)
  • `[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}` (メールアドレス)

このように、単なる単語リストではなく、「パターン」を定義できることこそが、このツールの真価である。

結論

自動化エンジニアの仕事は、単に「動くもの」を作ることではない。「失敗しない仕組み」を作ることだ。
今回紹介した再帰的探索と正規表現による設計は、PowerPointという複雑なオブジェクトモデルに対峙するための、一つの正解である。

このコードをベースに、自社の要件(ログ出力の追加、PDF変換の自動化など)を肉付けしていけ。それが、真のアーキテクトへの第一歩だ。

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