ExcelでWordを書き換える

仕事でExcelデータをWordのフォーマットにはめ込む必要に迫られました。Wordとか興味ないし、ネットからコピペしたコードでやろうかなと思ってググったのですが、やっぱり需要がないのかWordのVBAに関する情報はほとんどありません。解説しているサイトによってやり方もかなり違ってどれが正解かわかりません。結局リファレンスを読んでやりました。

同じ境遇のあなたがここにたどり着いたときにコピペで済ませられるようにしておきます。

Wordテンプレートエンジン作戦

Wordにデータを書き込むのにWord-VBAを覚えて「ここに段落を作って~文字サイズはこうで~フォントはこれで~」と操作するのは大変だし、その知識を今後活用する機会はおそらくやってきません。そこで私がオススメするのが名付けてWordテンプレートエンジン作戦です(※Wordでいうところのテンプレートとは異なります)。覚えるWord-VBAオブジェクトはFindだけです!

テンプレートエンジンとはWEBアプリなどでよく用いられるしくみで、あらかじめWEBページの大部分を作っておきます。そして、その時点では決定できないプログラムから動的に値をはめ込む部分を仮の値=プレースホルダーで埋めておきます。これがテンプレートです。

実際にページを表示する段階でテンプレートを読み込んで、仮の値を真の値に置き換えて表示します。こうすることで、プログラム実行時に生成しなければならない文字の量は最小限で済みます。

要するに、Wordの置換機能をやっているのと同じです。これと同じことをExcel-Wordでやれば「Wordテンプレートエンジン」の完成です。すでにあるWordファイルの文字を置き換えるだけなので、Wordをイチから組み立てていくよりはるかに簡単です。

まずはサンプルコードです。

Sub wordTemplateEngine()
    Dim templateDocPath As String
    templateDocPath = ThisWorkbook.Path & "\" & "city.docx"
    Dim dstDocPath As String
    dstDocPath = ThisWorkbook.Path & "\" & "saitama.docx"

    Dim wordApp As Object
    Set wordApp = CreateObject("Word.Application")
    Dim wordDoc As Object
    Set wordDoc = wordApp.Documents.Open(templateDocPath)
    
    Const PLACE_HOLDER As String = "@city@"
    Const CONTENT_TEXT As String = "さいたま市"
    
    wordDoc.Content.Find.Execute _
        FindText:=PLACE_HOLDER, _
        ReplaceWith:=CONTENT_TEXT, _
        Replace:=1
    wordDoc.SaveAs2 dstDocPath
    wordApp.Quit
End Sub

先の説明の通り、Excelの値を埋め込みたいWordファイルにはプレースホルダーをその部分に配置しておきます。サンプルコードの場合、定数PLACE_HOLDERで “@city@“です。後から文字の置き換えをするので、他では絶対に使用しないような文字の組み合わせにしましょう。じゃないと、置き換えたくない部分が置き換わってしまう可能性があります。

では、テンプレートとなるWordファイル(city.docx)にプレースホルダーを埋め込みます。次のようにしました。

Excel-VBAでサンプルコードを実行します。

新しいWordファイル(saitama.docx)ができました。次のようになりました。

解説

Wordファイルのインスタンスは次のように作成できます。

Dim wordApp As Object
Set wordApp = CreateObject("Word.Application")
Dim wordDoc As Object
Set wordDoc = wordApp.Documents.Open("Wordファイルのパス")

参照設定をすれば次のようにできます。ライブラリ名はMicrosoft Word *.* Object Libraryです。私は個人的に参照設定が嫌いなので最終的には外しますが、コーディングの時はつけておいた方がインテリセンスが効くので便利です。

Dim wordApp As Word.Application
Set wordApp = New Word.Application
Dim wordDoc As Word.Document
Set wordDoc = wordApp.Documents.Open("Wordファイルのパス")

余談ですが、最初にググった時に出てきたサイトがどこもかしこも参照設定をつけて

Dim wordApp As Word.Application
Set wordApp = CreateObject("Word.Application") ←

ってやっているんですよね。Newのほうがスマートでいいと思うんですけどね。何か私の知らない暗黙の掟があるのかも知れませんが、やっていることは同じなのでどっちでもいいんでしょうけどね。余談おわり。

テンプレートエンジンのキモはFindオブジェクトによる置き換え処理です。Executeメソッドの引数で挙動を指定できます。

wordDoc.Content.Find.Execute _
    FindText:=PLACE_HOLDER, _
    ReplaceWith:=CONTENT_TEXT, _
    Replace:=1

wordDoc.Contentオブジェクトは文書全体をあらわします(たぶん)。Contentオブジェクトの持つFindオブジェクトのExecuteメソッドが置き換え処理の実体です。引数はいっぱいあって、全部書くとキリがないので公式リファレンスを見てください。

今回使用した引数の意味は次の通りです。公式リファレンスより引用。

引数名説明
FindTextVariant検索する文字列を指定します。 書式だけを検索する場合は、空文字列 (“”) を指定します。 適切な文字コードを指定すると、特殊文字を検索できます。
ReplaceWithVariant置換文字列を指定します。 引数 Find によって指定された文字列を削除するには、空文字列 (“”) を使用します。 引数 Find と同じように、特殊文字や高度な検索条件を指定できます。
ReplaceVariant置換する文字列の個数 (1 つだけ、すべて、または置換しない) を指定します。 wdreplace定数のいずれかを指定できます。

引数Replaceに与える定数は次の3つがあります。

定数名説明
wdReplaceAll2すべて置き換える。
wdReplaceNone0置き換えない。
wdReplaceOne1最初に一致したもののみ置き換える。

テンプレートエンジンで複数箇所を同じ値に置き換える用途はほぼないと思います。ので、実質wdReplaceOne(参照設定しなければ1)固定になります。

実践編

たかだか1ヶ所を書き換えるだけであれば、Wordを直編集すればすむ話で、Excel-VBAの出番はありません。Excel-VBAを使う意義、いや、むしろこれ以外はオマケと言っても過言ではないループ処理に組み込んでテンプレートから違う内容のWordファイルを量産しましょう。

シートに埋め込みたい文字を入力しておきます。シート名はold_cityです。

A列を下へ走査していって、テンプレートのプレースホルダーをセルの値に順次置き換え保存していく処理を追加します。こうなりました。

Sub wordTemplateEngine()
    Const PLACE_HOLDER As String = "@city@"
    
    Dim templateDocPath As String
    templateDocPath = ThisWorkbook.Path & "\" & "city.docx"

    Dim wordApp As Object
    Set wordApp = CreateObject("Word.Application")
    
    Dim ws As Worksheet
    Set ws = Worksheets("old_city")
    
    Dim i As Long
    i = 1
    
    Do Until ws.Cells(i, 1).Value = ""
        Dim wordDoc As Object
        Set wordDoc = wordApp.Documents.Open(templateDocPath)
    
        Dim contentText As String
        contentText = ws.Cells(i, 1).Value
        
        wordDoc.Content.Find.Execute _
            FindText:=PLACE_HOLDER, _
            ReplaceWith:=contentText, _
            Replace:=1
            
        Dim dstDocPath As String
        dstDocPath = ThisWorkbook.Path & "\" & contentText & ".docx"
        
        wordDoc.SaveAs2 dstDocPath
        i = i + 1
    Loop
    
    wordApp.Quit
End Sub

ファイル名は同じにするとどんどん上書きされてしまうので、置き換える文字と同じ名前にしています。ちなみに別名保存のメソッドがSaveAs2と変な名前ですが、タイプミスではありません。これであっています。公式リファレンス通りです。

VBAでは、というかプログラミング言語全般ではオブジェクトを普通にコピーすると変数は複数になっても実体は同じものになります。参照型とかポインタとか難しい話が出てくるので、詳しくはグーグル先生へ。コピーした変数をいじると、コピーもとの変数のオブジェクトも影響を受けます。

そうならないようにディープコピーというしくみを備えている言語もあるのですが、VBAではできないようです。なので、都度テンプレートのWordファイルを開いて読み込んでいます。すっごい無駄に感じますがやむを得ず。ただ体感としてはもたつくこともなく相当速いので、実際にストレージからデータを読んでいるのは最初のOpenだけで、あとはメモリ上で完結しているんじゃないかという気がします。

実行すると新しくファイルができます。

開いてみると

このようにそれぞれ違う内容で保存されました。

試しに埼玉県の全市町村名をシートにコピペしてやってみました。

ちょっと小さくて見づらいですが、問題なくできています。

一つ開いてみると

中身もちゃんと置き換わっていますね。


Wordは紙で出してなんぼなので、ファイルに保存しなくていいから即印刷したいという場合は、SaveAs2のくだりを次のようにします。

wordDoc.PrintOut
wordDoc.Close SaveChanges:=0

実行すると結果がプリントアウトされます(さすがに全市町村を印刷するのはキツいので最初のやつです)。

プリントスプーラーが効いていないような、ややぎこちない印刷のされかたでしたが、まぁ結果オーライとします。たぶんページとして追加していって一気に出力すればスマートに印刷されるのでしょうが、そこまでのやり方を学習するコストをかける価値はないのでやめます。


ファイル数が増えるとメモリを食いまくりそうですが、私の環境では大きな変動はありませんでした。処理時間は埼玉県の全市町村64ファイル生成で数秒でした。

なお、最近PCを新調したので、普通の事務用へぼPCが100台束になっても相手にならない性能になってしまったのであまり参考にならないかも知れません(自慢)。

まとめ

まだまだ日本では社内の手続きですら書類が幅をきかせています。書類作りなんていう誰もやりたくない退屈な作業はコンピューターにやらせて楽をしましょう。

複数のプレースホルダーを埋め込む場合は、シートに対応表を作っておいて、それをVariantで取り込めば配列としてまわせるので、いかようにもできます。

Wordの使命は印刷するため、見るためのデータの作成であり、神エクセルを駆逐する手法として、今回のやり方は意外にありなんじゃないかと思いましたとさ。

おわり。