WordでDocument.Charactersオブジェクトを一つづつ処理するのは異様に時間がかかる
資料の修正箇所に網掛けをしたものと、同じ内容で網掛けのないバージョンを要求されることがある。
そして、網掛けありバージョンと網掛けなしバージョンが併立するということが起こる。
ここまではまあ良い。
ヒサンなのは次のようなケースだ。
- 資料に再修正の指示が出る。
- 再修正し、網掛けありバージョンと網掛けなしバージョンを作成する
- 再修正を繰り返すうちに、どこかで保存のタイミングを誤る
- 網掛けありバージョンと網掛けなしバージョンの内容にずれが生ずる
およそ、事務系の仕事をしていれば、誰でも経験するのではなかろうか。
VBAで網掛けを外す
……と、ここまで「網掛け」という用語を用いているが、実際にはHighLight
機能を使うことにする。「蛍光ペン」というやつだ。白黒印刷すれば、網掛けとほぼ同じ効果なので、ひとまずこれで代用する。
やり方としては、Document
オブジェクトのCharacters
コレクションの要素を一つづつ取り出して、HighLightIndex
プロパティがwdYellow
だったらwdNoHighLight
に変える、というだけのものとする。
リスト1 標準モジュール
Public Sub test() Dim winAPI As New WindowsAPI '……(1)' Dim startTime As Long startTime = winAPI.getNowTickCount Dim Doc As Document '……(2)' Set Doc = ThisDocument Dim targetChar As Range For Each targetChar In Doc.Characters '……(3)' With targetChar If .HighlightColorIndex = wdYellow Then '……(4)' .HighlightColorIndex = wdNoHighlight End If End With Next Dim endTime As Single '……(5)' endTime = (winAPI.getNowTickCount - startTime) / 1000 Call MsgBox("処理時間:" & endTime & vbCrLf & _ "文字数 :" & Doc.Characters.Count) End Sub
(1)からの3行
Dim winAPI As New WindowsAPI Dim startTime As Long startTime = winAPI.getNowTickCount
はお気になさらねえでくだせえ。
WinAPI
クラスというのは、割とよく使うWindowsAPI
の関数を、手軽に使えるように自作のクラスモジュールにまとめたもの。
ここでは、あの有名なGetTickCount
関数をラップしていると思ってください。
メインの処理は(2)からの10行
Dim Doc As Document Set Doc = ThisDocument Dim targetChar As Range For Each targetChar In Doc.Characters '……(3)' With targetChar If .HighlightColorIndex = wdYellow Then '……(4)' .HighlightColorIndex = wdNoHighlight End If End With Next
Document.Characters
コレクションの要素を一つづつ取り出して、HighlightColorIndex
プロパティの値によって処理を切り替えている。
Characters
コレクションの要素がCharacter
オブジェクトではなく、Range
オブジェクトだというあたりが注意かな。
中身も詳しく見ておく。(3)からの7行
For Each targetChar In Doc.Characters With targetChar If .HighlightColorIndex = wdYellow Then '……(4)' .HighlightColorIndex = wdNoHighlight End If End With Next
では、For Each ~ Next
を用いて、Characters
コレクションの要素一つ一つを処理する。
ループ内部では、(4)からの3行
If targetChar.HighlightColorIndex = wdYellow Then targetChar.HighlightColorIndex = wdNoHighlight End If
で、Characters
コレクションの要素であるRange
オブジェクト(変数targetChar
の中身)のHighlightColorIndex
プロパティの値を調べ、値がwdYellow
だったら、すなわち黄色マーカが施されていたら、HighlightColorIndex
プロパティの値をwdNoHighlight
に変える、すなわちマーカを除去する、という処理を行う。
残りの、(5)からの4行(実質3行)
Dim endTime As Single endTime = (winAPI.getNowTickCount - startTime) / 1000 Call MsgBox("処理時間:" & endTime & vbCrLf & _ "文字数 :" & Doc.Characters.Count)
は、(1)の続き。処理後の時間を取得して、処理に掛かった時間を表示するだけ。
実行
このようなWord文書を用意する。文書内には、ご覧のようにところどころ黄色マーカを施してあるw
この状態で、リスト1を実行してみる。ちなみに、画面左下の文字数カウントでは「1082
文字」と出ている。
なんと、3.785
秒もかかっている。しかも、なぜか文字数(正確にはDocument.Characters.Count
の値だけど)は1218
になっている。
おわりに
1000
字チョイなんて、コンピュータにとっては屁みたいな文字数だと思ったのだが、なぜこんなにヒマがかかるのだろうか。
しかも、今回のコードは単にマーカ部分をマーカなしにするだけであり、ひとたび実行すると、元の状態を復元することはできない(WordのVBAの場合、アンドゥでそこそこ戻れるけど。)。
マーカあり/なしを切り替えるには、マーカされている部分を記憶しておく必要がある。そのためには状態を取得するためにCharacters
オブジェクトの要素にアクセスする必要がある。
たぶん、やり方がマズいのだろうけど、何か良いやり方はないものか。