[Excel/VBA] 文字列の書式指定出力・埋込

# 1. はじめに

Excel/VBA の文字列の書式指定関数といえば `WorksheetFunction.Text` 関数や `Format` 関数がありますが、これらはいずれも変換対象となる文字列をひとつしか指定できません。

私は書式指定する際は複数の文字列を書式変換しつつ結合・埋込することが多く、いちいち `&` でつなぐのが煩わしく感じるうえにコードも見づらくなるので、`printf` のような関数を Windows の VBA 環境で使えたら楽なのにな～と思うことがけっこうあります。



# 2. 環境

私の環境は次のとおりです。

* OS : Windows 10
* Office : Excel 2016

# 3. 書式を指定して出力する方法

## 3.1. WorksheetFunction.Text を使う

Excel で書式指定するといえばこれです。この関数の弱いところが、冒頭でふれた、引数が2つ固定という点です。複数個カンマ区切りで表記したい数値がある場合は `=TEXT(4001, "#,###")&TEXT(1234, "#,###")` のように `&` でつながなくてはなりません。

また、 `printf` とは引数の並び順が逆で、個人的には割とよく間違えます。あと `printf("%7d", 1200)` とか `printf("%7.3f", 125.05)` などが VBA でどう書けば楽かがわからない。


## 3.2. Format を使う

`WorksheetFunction.Text` と `Format` の違いが正直よくわかりません。受取る引数の並び順、指定できる引数が2つ固定など基本同じ関数だと考えてもいいんでしょうかね？


## 3.3. MSYS/MinGW の printf を使う

MSYSやMINGWのprintfを WSH 経由でむりやり呼出すことができます。[こちら](http://officetanaka.net/excel/vba/tips/tips27.htm)で紹介されているコードを拝借しました。

```vb
Public Function Sprintf(ByVal format_ As String, ParamArray args() As Variant) As String
    Dim wsh      As Object
    Dim wsh_exec As Object
    Dim result   As String
    Dim params   As String
    Dim arg      As Variant

    For Each arg In args
        params = params & " " & CStr(arg)
    Next arg

    Set wsh = CreateObject("WScript.Shell")
    Set wsh_exec = wsh.Exec("C:\tools\msys64\mingw64\bin\printf " & format_ & " " & params)

    Do While wsh_exec.Status = 0
        DoEvents
    Loop

    Sprintf = wsh_exec.StdOut.ReadAll

    Set wsh_exec = Nothing: Set wsh = Nothing
End Function
```

エスケープシーケンスを使用して改行やタブを表現できますが、実行するたびに、画面上に一瞬DOS窓が表示されてしまいます。


## 3.4. .NET Framework の StringBuilder を使う

最近の Windows ならほぼ間違いなく入っている（と思う） .NET Framework に、 COM として呼出せる `System.Text.StringBuilder` クラスがあります。通常は文字列の連結に用いられるクラスですが、このクラスには書式指定しつつ文字列を結合する `AppendFormat` というメソッドがあります。このメソッドを文字列の結合ではなく、書式設定のために使ってしまおうというわけです。

[Microsoft のリファレンス](https://msdn.microsoft.com/ja-jp/library/system.text.stringbuilder(v=vs.110).aspx) を見るとオーバーロードがたくさんあります。[こちら](http://blog.livedoor.jp/midorityo/archives/50744656.html)で VBA での `StringBuilder` のラッパを公開されていますが、このうちの `AppendFormat_4` を利用すれば、結合する引数の数にかからわず統一的にあつかえるので楽そうですね。

```vb
' System.Text.StringBuilder AppendFormat_4(string format, Params System.Object[] args)

Dim str_builder As Object: Set str_builder = CreateObject("System.Text.StringBuilder")
Debug.Print str_builder.AppendFormat_4("{0}の最強の具は{1}である。", Array("おにぎり","筋子")).ToString
' => "おにぎりの最強の具は筋子である。"
```

`StringBuilder` を利用すれば、[こちら](https://msdn.microsoft.com/ja-jp/library/dn906227(v=vs.110).aspx)の関連項目に貼られているリンク先にあるような豊富な種類の書式が指定できるようになります。


## 3.5. その他

`VBA printf` などで Google 検索をかけてみると、[こちら](http://thom.hateblo.jp/entry/2015/03/08/000000)でも自作の `printf` を公開されていますし、[スタックオーバーフロー](http://stackoverflow.com/questions/17233701/is-there-an-equivalent-of-printf-or-string-format-in-excel)でも質問が上がっています。やっぱりある程度の需要はあるんでしょうかね。

あとは自作という手もありますが…。



# 4. おわりに

多彩な書式を利用しつつ `printf` 的に複数個の引数を渡すなら、 `StringBuilder` がよさそうです。

私がしょっちゅう使うのは `=TEXT(505001, "#,###")`(→ `505,001` ) のような数値のカンマ区切りや、 `=TEXT(12345, "00000000")` のゼロ埋桁指定、 `=TEXT(42865, "ggge年m月n日")`(→ `平成29年5月10日` ) といった日付の書式指定（特に和暦表記）なので、普通に `printf` を使っても指定できない書式なんですよね。おとなしく `Format` を使っておきましょうかね…。