WordのVBAを使って指定した文字列（集合）に蛍光マーカーをつける。その際、テキスト本体だけでなく図形オブジェクトで作成したテキストエリア内にも反映させる。

# 1. はじめに
ワードで作成した文書に、ある特定の文字列（集合）に蛍光マーカーで着色したいと思ったことはないだろうか。たとえば作成していた資料の校正を行うために用語のゆらぎを調べたり、あるいは目次を作るために索引語をマーキングしたりと、用途はいろいろありそうだ（Fig.1参照）。

![サンプル.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3538461/ac2cc66a-03cd-0add-5b89-ab23bcedd5d5.jpeg)
***Fig.1 蛍光マーキングの例　本文以外に２つのテキストエリア内もマーキングしている。***

ネットでその方法を検索すると、あるにはあるのだが、それでやってみても本文だけはマーキングできるのだが、図形オブジェクトのテキストアリア内に打ち込んだ文章にはマーキングされないなどいろいろトラブルに出くわした経験はないだろうか。

テキストアリアも処理対象にしたいと検索キーワードに「ワード VBA テキストエリア 文字列 蛍光マーカー」など入力して検索するのだがなかなか的を射たコンテンツが見つからない。ChatGPTに尋ねても嘘ばかり教えられる。そういった経験はないだろうか。

そういった方々のために私が悪戦苦闘した解決方法を備忘録として書き留めることにした。

# 2. 概要
要点は ```Selection``` オブジェクトの ```Find``` メソッドで指定した文字列を検索し、```Selection.Range.HighlightColorIndex``` に蛍光マーカーの色番号を設定するだけである。とはいえ、不慣れな人には何をしてよいのやらわからない。そこで、具体例を交えながら一つずつ説明していく。

## 2.1 Selection オブジェクト
```Selection```オブジェクトは文字列をマーキングする際のターゲット（Word文書本体なのか図形オブジェクトのテキストエリアなのか）である。```Select```メソッドでターゲットを選択して、あとは```Selection```オブジェクトを使って検索文字列を ```Find``` し、見つかったら ```Find``` オブジェクトの ```Execute``` メソッドを見つかった数だけ繰り返して蛍光ペンの色を設定する（たとえば黄色の蛍光ペンなら ```Selection.Range.HighlightColorIndex = wdYellow```）だけだ。
具体的な例を示そう。

```VB:コード１
        With Selection.Find
            Selection.Move wdStory
            .Text = buf
            .Replacement.Text = buf
            .Wrap = wdFindContinue
            Do While .Execute
                Selection.Range.HighlightColorIndex = wdYellow
            Loop
        End With
```
ここで、```buf```は蛍光マーキングしたい文字列である。
では、```Selection```オブジェクトはいまどこをターゲットにしているのだろうか？本文だろうか？それとも図形オブジェクトのテキストエリアだろうか？
実はそのターゲットは意識して選択（```Select```）しなければならない。何も書かずに上記のコードを実行すると文書本体がターゲットになるみたいだ。通常は文書本体しかマーキングする必要性を感じないだろうからこれで片が付く。しかし、凝った文書を作成して図形オブジェクトのテキストエリアを多用して、重要な内容をその中に詰め込んだらどうなるだろう？上記のコードはテキストエリアの中までマーキングしてくれない。
そこで登場するのが ```Select```メソッドである。

## 2.2 Select オブジェクト
```Select```オブジェクトはターゲットを選択する。たとえばWordの文書本体であれば
```VB:コード２
ActiveDocument.Range.Select
```
とすればよい。ここで、```ActiveDocument```は現在アクティブになっている文書である。Word文書内のマクロであれば、今開いているWord文書それ自身だ。だが、普通こんなものは書かない。書かないでも文書に付属するマクロが実行する時点で現在開いている文書のドキュメント（本体）が```Select```されているからだ。しかし、たまたま何か処理で別のオブジェクトが```Select```されていたらどうなるだろう？コード１は一生懸命その```Select```されたオブジェクトにマーキングしようとすることだろう。だから、その場合の安全策として本文を対象としたい場合はいつでもコード１に先立ってコード２を書いておけばよい。

では、図形オブジェクトのテキストアリアを```Select```するにはどうすればよいか。コード３がそれである。
```VB:コード３
    For Each shp In ActiveDocument.Shapes
        If shp.Type = msoTextBox Then
            shp.TextFrame.TextRange.Select
            Call [任意の蛍光マーキング処理]
        End If
    Next
```
```ActiveDocument.Shapes```は現在開いているWord文書のすべての図形オブジェクトを表すコレクションで、そこから一つずつ```Shape```オブジェクトを```shp```に取り出して```Shape```オブジェクトの```TextFrame```オブジェクトの中にある```TextRange```オブジェクトを```Select```メソッドで選択する。それが```shp.TextFrame.TextRange.Select```である。ただし、コード３では```Shape```オブジェクトの```Type```プロパティで図形オブジェクトがテキストエリア（```msoTextBox```）であることを確認してからターゲットに選択している。やっていないのでわからないが、他のオブジェクト（たとえば矩形オブジェクト）でも、その中にテキスト領域があればこのコードでマーキングができるかもしれない。こうしてターゲットが選択（```Select```）できたら任意の蛍光マーキング処理を呼び出す。

# 3. WordマクロとExcelマクロ
さて、概要が分かったところで実際にコードを作っていこう。その際、考えなければならないのは、そのコードをどこに書くかである。どこに、というのはWordファイルそのものにか、それともVBAが利用できる他のファイル、たとえばExcelファイルにか、ということである。
もっとも簡単なのはWordファイルそのものに書くことである。これまで紹介したコードはすべてWordファイル本体に書くコードを想定していた。しかし、Word本体にコードを書くと様々な問題点が出てくる。まず最初に、これが一番問題となるが、ファイルの種類をWordファイル（```.docx```ファイル）ではなくマクロ付きWordファイル（```.docm```ファイル）に変換しなければならない。自分の自由にしてよいWordファイルならともかく、チーム内や公に共有（公開）するファイルだとまず無理である。
そこで、次善の策として、VBAが利用できるOffice関連ファイル、あるいはVBScriptを利用することが考えられる。ここでは最もVBAが利用しやすいExcelファイルを用いる例を紹介する。

## 3.1 Wordマクロ
Wordマクロを使う場合、前準備として２つの作業がある。一つ目は、前述したように、ファイルの種類をWordファイル（```.docx```ファイル）からマクロ付きWordファイル（```.docm```ファイル）に変換すること、二つ目はメニューに［開発］ボタンを表示することである。Word で [開発] タブを表示するには[このサイト](https://support.microsoft.com/ja-jp/office/word-%E3%81%A7-%E9%96%8B%E7%99%BA-%E3%82%BF%E3%83%96%E3%82%92%E8%A1%A8%E7%A4%BA%E3%81%99%E3%82%8B-e356706f-1891-4bb8-8d72-f57a51146792)を参考に設定を行なえばよいだろう。
［開発］ボタンをクリック後、VIsualBasicエディタを開いて新規に標準モジュールを作成して次のコードを入力する。
```VB:コード４
Option Explicit

Sub 蛍光マーキング()
Dim strArray() As String

    strArray = Split("急性骨髄性白血病:白血病", ":")
    
    Call 本文蛍光マーキング(strArray)
    Call テキストエリア蛍光マーキング(strArray)
End Sub

Sub 本文蛍光マーキング(strArray() As String)
    ActiveDocument.Range.Select
    Call 選択蛍光マーキング(strArray)
End Sub

Sub テキストエリア蛍光マーキング(strArray() As String)
Dim shp As Shape
     
    For Each shp In ActiveDocument.Shapes
        If shp.Type = msoTextBox Then
            Debug.Print shp.Name
            'Debug.Print shp.TextFrame.TextRange.Text
            shp.TextFrame.TextRange.Select
            Call 選択蛍光マーキング(strArray)
    
        End If
    Next
    
End Sub

Sub 選択蛍光マーキング(strArray() As String)
Dim buf As Variant

    For Each buf In strArray
        With Selection.Find
            Selection.Move wdStory
            .Text = buf
            .Replacement.Text = buf
            .Wrap = wdFindContinue
            Do While .Execute
                Selection.Range.HighlightColorIndex = wdYellow
            Loop
        End With
    Next
    
End Sub
```
メイン関数は```Sub 蛍光マーキング()```で、まず、マーキングする文字列配列```strArray```を組み立てたのちに本文をマーキングするプロシージャ```本文蛍光マーキング(strArray)```を呼び出し、ついで図形オブジェクトのテキストアリアをマーキングする```テキストエリア蛍光マーキング(strArray)```を呼び出す。

```本文蛍光マーキング(strArray)```は、マーキング文字列配列```strArray```を受け取り、```ActiveDocument.Range.Select```で本文を選択（```Select```）したのちに共通「マーキング」ルーチン```選択蛍光マーキング(strArray)```を呼び出す。
```テキストエリア蛍光マーキング(strArray() As String)```はマーキング文字列配列```strArray```を受け取り、```ActiveDocument.Shapes```から図形オブジェクトを一つずつ取り出して変数```shp```に格納し、それがテキストアリア（```msoTextBox```）であれば```shp.TextFrame.TextRange.Select```によってテキストアリアを選択（```Select```）し、共通「マーキング」ルーチン```選択蛍光マーキング(strArray)```を呼び出す。
共通「マーキング」ルーチン ```Sub 選択蛍光マーキング(strArray() As String)``` では受け取ったマーキング文字列配列 ```strArray``` から一つずつ文字列を取り出して変数 ```buf``` に格納し、それを ```Selection.Find``` メソッドで検索し、見つかったら ```Find````オブジェクトの```Execute```メソッドで ```Selection.Range.HighlightColorIndex = wdYellow``` によって黄色（```wdYellow```）にマーキングする。

## 3.2 Excelマクロ
Excelマクロを使う場合、前準備として３つの作業がある。最初の２つはWordマクロと同様にファイルの種類の変更（```.xlsx```ファイルから```.xlsm```へ）と[［開発］ボタンの表示](https://support.microsoft.com/ja-jp/office/-%E9%96%8B%E7%99%BA-%E3%82%BF%E3%83%96%E3%82%92%E8%A1%A8%E7%A4%BA%E3%81%99%E3%82%8B-e1192344-5e56-4d45-931b-e5fd9bea2d45#:~:text=%5B%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%5D%20%E3%82%BF%E3%83%96%E3%80%81%5B%E3%82%AA%E3%83%97%E3%82%B7%E3%83%A7%E3%83%B3,%E3%83%9C%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E3%82%AA%E3%83%B3%E3%81%AB%E3%81%97%E3%81%BE%E3%81%99%E3%80%82)で、残る一つは**参照設定**である。これは、Excel から Word オブジェクトを参照するための設定で、これをやっておくとコードのオートフィルが有効になり、効率的であると同時にミスタイプ防止や発見的コーディングが可能となって嬉しい。ネットで検索するプログラムの中には参照設定を行わず、何でもかんでも Object で宣言する例をよく見るが私は好きではない。さて、その参照設定だが、Visual Basic エディタ（略してVBエディタ）のメニュー画面から［ツール］→［参照設定］→［Microsoft Word **16.0** Object Library］にチェックを入れる。ただし、**16.0** の部分はマシン依存なので適宜読み替えて欲しい。

さて、準備が整ったら、新規に標準モジュールを作成して次のコードを入力する。
```VB:コード５
Option Explicit

Sub 蛍光マーキング()
    Dim wdApp As Word.Application
    Dim wdDoc As Word.Document
    Dim strArray() As String

    ' Wordアプリケーションを開く
    Set wdApp = New Word.Application
    wdApp.Visible = False
    
    ' Word文書を開く
    Set wdDoc = wdApp.Documents.Open(ActiveWorkbook.path & "\サンプル.docm")
    
    ' 病名を":"で区切る
    strArray = Split("急性骨髄性白血病:白血病", ":")
    
    ' 蛍光マーキング
    Call 本文蛍光マーキング(wdApp, strArray)
    Call テキストエリア蛍光マーキング(wdApp, strArray)
    
    ' Word文書を保存して閉じる
    wdDoc.Save
    wdDoc.Close
    
    ' Wordアプリケーションを終了
    wdApp.Visible = True
    wdApp.Quit
    
End Sub

Sub 本文蛍光マーキング(wdApp As Word.Application, strArray() As String)
    wdApp.ActiveDocument.Range.Select
    Call 選択蛍光マーキング(wdApp, strArray)
    
End Sub

Sub テキストエリア蛍光マーキング(wdApp As Word.Application, strArray() As String)
Dim shp As Word.Shape
    
    For Each shp In wdApp.ActiveDocument.Shapes
        If shp.Type = msoTextBox Then
            Debug.Print shp.Name
            'Debug.Print shp.TextFrame.TextRange.Text
            shp.TextFrame.TextRange.Select
            Call 選択蛍光マーキング(wdApp, strArray)
        End If
    Next
    
End Sub

Sub 選択蛍光マーキング(wdApp As Word.Application, strArray() As String)
Dim buf As Variant

    For Each buf In strArray
        With wdApp.Selection.Find
            wdApp.Selection.Move wdStory
            .Text = buf
            .Replacement.Text = buf
            .Wrap = wdFindContinue
            Do While .Execute
                wdApp.Selection.Range.HighlightColorIndex = wdYellow
            Loop
        End With
    Next
    
End Sub
```
Word マクロとの違いに留意して説明する。もっとも大きな違いは、Excel で Word 文書を操作するには Word アプリケーションオブジェクト ```Word.Application``` を使う。 また、処理対象の Word ドキュメントをインスタンス化するために Word ドキュメントオブジェクト ```Word.Document``` を使う。両者はメイン関数 ```Sub 蛍光マーキング()``` で定義しインスタンス化する。コード５では、各々 ```wdApp``` と ```wdDoc``` という名前で定義している。Word ドキュメントオブジェクト ```wdDoc``` は Word アプリケーションオブジェクト ```wdApp``` の ```Documents.Open``` メソッドを使ってインスタンス化する。引数には Word 文書ファイルを指定する。```ActiveWorkbook.path``` は現在この Excel ファイルが格納されているパスが返されるので、Excel ファイルと同じフォルダに格納された Word 文書ファイル ```サンプル.docm``` が開かれる。

マーキングする文字列配列```strArray```を組み立てたのちに本文をマーキングするプロシージャ```本文蛍光マーキング(wdApp, strArray)```を呼び出し、ついで図形オブジェクトのテキストアリアをマーキングする```テキストエリア蛍光マーキング(wdApp, strArray)```を呼び出すのは Word マクロの場合と同様である。違うのは、各々のプロシージャの第一引数に Word アプリケーションオブジェクト ```wdApp``` が引き渡されている点である。これはマーキング処理のターゲットを選択（```Select```）する際に必要となる。なぜなら、Word マクロでは対象とする Word 文書は ```ActiveDocument``` で参照できたが、Excel マクロではそれが ```wdApp.ActiveDocument``` となるからである。これを考慮して本文をマーキングするプロシージャ ```Sub 本文蛍光マーキング(wdApp As Word.Application, strArray() As String)``` とテキストアリアをマーキングするプロシージャ ```Sub テキストエリア蛍光マーキング(wdApp As Word.Application, strArray() As String)``` を修正する。

最後に、共通「マーキング」ルーチン ```Sub 選択蛍光マーキング(wdApp As Word.Application, strArray() As String)``` も Word アプリケーションオブジェクト ```wdApp``` を受け取る。これは ```Selection``` オブジェクトが Word のものだと明確に指定するためである（単に ```Selection``` とすると、それは Excel の ```Selection``` オブジェクトとみなされる）。

全ての蛍光マーキング処理が終了したら、Word 文書を保存して閉じてから Word アプリケーションを終了させる。

# 4. おわりに
作成したマクロを使って Word のサンプル文書に対して蛍光マーキングを行った結果が Fig.1 である。この文書には本文以外に２つのテキストエリアがあるが、指定した文字列（この場合は「急性骨髄性白血病」と「白血病」）が本文に対してもテキストエリアに対しても蛍光マーキングされているのがわかる。

今回紹介した蛍光マーキングに限らず、Word 文書に対して様々な処理を行う際に、いちいち Word 文書にマクロを書くのは運用面でも得策ではなく Excel ファイルを Word 文書のハンドラとして利用することが多いのではないか思う。その際のテンプレートとして今回紹介した Excel マクロが利用できるのではないかと思う。