【実務・中級編】【中級者向け】メール本文中の「社内用語」を自動で「正式名称」に置換する校正支援機能 – Outlook VBA解析バイブル

スポンサーリンク

【Outlook VBA】送信前の自動検知と動的置換:社内用語校正支援システムの実装

開発プロジェクトのリーダーである私たちが、現場のメンバーから最もよく受ける相談の一つが、「クライアントへのメール送信時に、うっかり社内用語や略語を使ってしまい冷や汗をかいた」というヒューマンエラーの対策だ。

「ASP」「社内ニート」「〇〇コンペ」……社内では通じる言葉も、一歩外に出ればマナー違反や誤解を招く原因になる。これを個人の注意力に頼るのは、システム開発の観点から言えば「設計の怠慢」だ。

今回は、Outlookの送信イベント(`ItemSend`)をフックし、メール本文中の社内用語を検知・自動置換、あるいは警告する実務直結の校正支援システムを構築する。
単なる「文字列置換のコード」ではない。実務のパフォーマンス要件や、ファイルI/Oのボトルネックを完全にクリアしたプロダクションコードを伝授しよう。

—

1. なぜ「力技の文字列置換」は実務で破綻するのか?

多くのプログラマーが最初に書くアプローチはこうだ。
「本文(`MailItem.Body` または `HTMLBody`)を取得し、`Replace`関数を何重にもネストして回す」

この実装には、以下の致命的な欠陥がある。

1. HTMLタグの破壊
ModernなOutlookメールはHTML形式が主流だ。`Body`に対して単純な文字列置換を行うと、`` などのHTMLタグ内に偶然含まれていた社内用語まで置換されてしまい、メールのデザインが完全に崩壊する。
2. パフォーマンスの劣化
辞書データ(置換前・置換後リスト)が数十件を超え、さらに宛先が多い環境で毎回の送信時にテキスト全体をスキャン・置換し続けると、COMの往復が発生してOutlookが一時的にフリーズ(Not Responding)する。
3. 誤爆(部分一致)のリスク
例えば「PJT(プロジェクト)」という用語を置換する際、英語の単語の一部にヒットしてしまい、全く意図しない文字列に変形してしまう。

これらを解決するためには、「HTMLの構造を壊さないプレーンテキスト抽出(あるいは正規表現の活用)」と、「外部辞書ファイル(CSV/Excel)のスマートなキャッシュ読み込み」が不可欠だ。

—

2. アーキテクチャ設計

今回のシステムの全体像は以下の通り。

  • イベントフック: `Application_ItemSend` を利用し、送信ボタンが押された瞬間に割込みを実行。
  • 辞書データ: メンテナンス性を考慮し、Excelファイル(またはCSV)からメモリ上(Dictionaryオブジェクト)へロード。毎回のファイルアクセスによる遅延を防ぐ。
  • 置換対象: HTMLメールであっても、安全に処理するため、テキスト部分(`Inspector` または `WordEditor`)を操作するか、厳密にプレーンテキスト領域をターゲットにする。今回は実用性を重視し、Wordドキュメントオブジェクトモデル(`Word.Document`)を背後で操作するアプローチを採用する。

—

3. プロダクションコード実装

以下のコードを、OutlookVBAの `ThisOutlookSession` モジュールおよび標準モジュールに配置してほしい。

① 辞書データ(CSVのイメージ)

保守性を高めるため、置換リストは外部のCSVファイル(例: `C:\OutlookConfig\dictionary.csv`)に切り出す。

社内ニート,正社員
PJT,プロジェクト
MTG,ミーティング
先方様,お客様

② 標準モジュール:校正ロジックの実装

Option Explicit

‘ 辞書ファイルのパス
Private Const DICTIONARY_PATH As String = “C:\OutlookConfig\dictionary.csv”

Public Sub ExecuteProofread(ByVal Item As Object, ByRef Cancel As Boolean)
Dim mail As MailItem
Dim dict As Object
Dim doc As Object ‘ Word.Document
Dim key As Variant
Dim replaceCount As Long
Dim msgResult As VbMsgBoxResult

If Not TypeOf Item Is MailItem Then Exit Sub
Set mail = Item

‘ 1. 辞書データの読み込み(Dictionaryオブジェクト化)
Set dict = LoadDictionary(DICTIONARY_PATH)
If dict.Count = 0 Then Exit Sub ‘ 辞書が空ならスルー

‘ 2. Inspectorを開かずにWordEditorから安全に本文テキストを操作
‘ ※送信前チェックのため、一時的にWordドキュメントとして本文をラップする
On Error GoTo ErrorHandler
Dim insp As Inspector
Set insp = mail.GetInspector
Set doc = insp.WordEditor

replaceCount = 0

‘ 3. 高速置換処理(WordのFind/Replace機能を利用することでHTMLタグを保護)
Dim rng As Object
Set rng = doc.Content

For Each key In dict.Keys
rng.Find.ClearFormatting
rng.Find.Replacement.ClearFormatting

rng.Find.Text = CStr(key)
rng.Find.Replacement.Text = CStr(dict(key))

‘ 前方一致・後方一致の誤爆を防ぐため単語単位、かつすべて置換
rng.Find.Forward = True
rng.Find.Wrap = 1 ‘ wdFindContinue
rng.Find.Format = False
rng.Find.MatchCase = True ‘ 大文字小文字を区別
rng.Find.MatchWholeWord = False

‘ 置換実行とカウント
Do While rng.Find.Execute(Replace:=2) ‘ wdReplaceAll
replaceCount = replaceCount + 1
Loop

‘ 検索レンジをリセット
Set rng = doc.Content
Next key

‘ 4. 置換が発生した場合のユーザー確認
If replaceCount > 0 Then
msgResult = MsgBox(“送信前の校正支援システムが ” & replaceCount & ” 件の社内用語を正式名称に自動置換しました。” & vbCrLf & _
“このまま送信を続行しますか?” & vbCrLf & _
“([いいえ] を押すと送信をキャンセルし、修正内容を確認できます)”, _
vbYesNo + vbExclamation, “メール校正チェッカー”)

If msgResult = vbNo Then
Cancel = True
Exit Sub
End If
End If

Set dict = Nothing
Exit Sub

ErrorHandler:
MsgBox “校正処理中にエラーが発生しました: ” & Err.Description, vbCritical
Cancel = True
End Sub

‘ 外部CSVから辞書をメモリにロードする関数
Private Function LoadDictionary(ByVal filePath As String) As Object
Dim dict As Object
Set dict = CreateObject(“Scripting.Dictionary”)

If Dir(filePath) = “” Then
‘ ファイルが存在しない場合は空のDictionaryを返す
Set LoadDictionary = dict
Exit Function
End If

Dim fileNum As Integer
fileNum = FreeFile

Dim lineData As String
Dim pos As Long
Dim key As String, val As String

Open filePath For Input As #fileNum
Do While Not EOF(fileNum)
Line Input #fileNum, lineData
pos = InStr(lineData, “,”)
If pos > 0 Then
key = Trim(Left(lineData, pos – 1))
val = Trim(Mid(lineData, pos + 1))
If key <> “” And Not dict.Exists(key) Then
dict.Add key, val
End If
End If
Loop
Close #fileNum

Set LoadDictionary = dict
End Function

③ ThisOutlookSession:イベントのフック

Option Explicit

Private Sub Application_ItemSend(ByVal Item As Object, Cancel As Boolean)
‘ 送信ボタン押下時に校正ロジックをコール
ExecuteProofread Item, Cancel
End Sub

—

4. この設計が「プロフェッショナル」である理由

1. `WordEditor` オブジェクトの活用
Outlookのメール本文の背後には、Microsoft Wordのエンジン(`Word.Editor`)が動いている。生のHTML文字列を正規表現で書き換えるような愚行をせず、Wordが持つ本来の検索・置換エンジン(`Find / Replace`)を利用することで、フォント装飾やハイパーリンク、画像などのHTML構造を一切破壊せずにテキストだけを安全に置換している。
2. `Scripting.Dictionary` によるO(1)の高速ルックアップ
辞書データを毎回ファイルから読み込むのではなく、最初にメモリ上にハッシュマップとして展開する。これにより、組織内で何百語という辞書に膨れ上がったとしても、パフォーマンスの低下を最小限に抑えられる。
3. フェイルセーフな割り込み設計
`Cancel = True` の制御を明確にし、万が一エラーが発生した場合や、ユーザーが置換結果に納得がいかず「いいえ」を選んだ場合は、即座にメールの送信を中断(キャンセル)する。誤送信の防止において「止まるべきときは確実に止まる」ことは、機能の豊富さよりも重要だ。

—

5. 導入・運用時の注意点

  • セキュリティ設定の壁: 組織のグループポリシー(GPO)やセキュリティソフトによっては、VBAによる `WordEditor` へのアクセスやマクロの実行が制限されている場合がある。事前に検証環境でテストを行ってほしい。
  • 辞書ファイルの置き場所: 複数人で同じ辞書を共有する場合、ローカルPC(`C:\…`)ではなく、社内のファイルサーバーやSharePointの同期フォルダを指定することで、全社的な用語のアップデートが一元化できる。

「メールの品質は、個人の意識ではなく、システムのデザインで担保する。」
このコードを導入した瞬間から、あなたのチームのビジネスメールにおける信頼性は、一段上のステージへと引き上げられるはずだ。

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