会社でRuby使用禁止になったのなら、VBAでテンプレートエンジンを使えばいいじゃない

顛末

 Rubyで有名なテンプレートエンジンにERBがあります。これとExcelいじるコードを組み合わせていろいろハッピーなスクリプトを書いていたのですが、保守環境では(オトナの事情で)Rubyを使えないので困る、という話もあり、Excelの話はVBAで片付けようという気持ちにもなってきました。

VBAで使えるテンプレートエンジンはあるのかな?

 そういう訳でVBAでも使えるテンプレートエンジンはないかな?とググってみたところ、どうやらMini Templetorというエンジンがあることが分かりました。HTML用とか書いてありますが、別にそんなの関係ねえはずです。

 GIGAZINEでは世界のPHPテンプレートエンジンとか言って紹介されていますが、JavaPHPVBAで使えます。超優秀です。

そんな訳でMini Templatorを使って簡単なサンプルを作ってみた


 上図のようなレイアウトで、SQL作成ボタンを押すとCREATE TABLE文を生成してくれるようなVBAマクロを作成してみます。

SQL文のテンプレート

 以下のようなものを用意しました。

CREATE TABLE ${schema}${tableName}
(<!-- $BeginBlock columns -->
	${columnName}	${columnType}	${constraint}<!-- $EndBlock columns -->
)
/
comment on table ${schema}${tableName} is '${tableComment}'
/
<!-- $BeginBlock comments -->comment on column ${schema}${tableName}.${columnName} is '${columnComment}'
/
<!-- $EndBlock comments -->

 ちょっと改行がおかしいのはオトナの事情です。これにcreateTemplate.tblという適当な名前をつけて保存しました。

VBAマクロのコード

 まずはMiniTemplator(MiniTemplator.cls)をクラスモジュールにインポートします。すると下図のようにクラスモジュールにMiniTemplatorが表示されます。

 今回はデータ構造を保存するためにcolumnInfo*1とTableInfoというクラスを作成しています。

' columnInfo
Public columnName As String
Public columnType As String
Public constraint As String
Public comment As String
' TableInfo
Public schema As String
Public tableName As String
Public comment As String

 そして以下のようなコードでSQLを生成します。

' テンプレートファイル名
Private Const templateFileName = "createTemplate.tbl"
Private Const startRow = 5
Private Const startCol = 2

Private Sub SQL生成_Click()
    ' 必要な情報があるかをチェック
    ' テーブルの名前は必須
    If Range("tableName") = "" Then
        MsgBox "テーブル名は必須です。テーブル名を入力して下さい。"
        Exit Sub
    End If
    ' カラム情報は一つ以上必要
    For Each v In Range("B5:C5")
        If v = "" Then
            MsgBox "カラムは一つ以上必須です。カラム情報を入力してください。"
            Exit Sub
        End If
    Next v
    
    ' テーブル情報を格納する変数
    Dim table As New TableInfo
    ' テーブル情報を格納する変数
    Dim column As columnInfo
    ' カラム情報のコレクション
    Dim columnArray As New Collection
    
    ' テーブル情報を格納
    If Range("schema").Value <> "" Then
        table.schema = Range("schema").Value & "."
    Else
        table.schema = ""
    End If
    table.tableName = Range("tableName").Value
    table.comment = Range("tableComment").Value
    
    'カラム情報を格納
    Dim currentRow As Integer
    currentRow = startRow
    Do While Cells(currentRow, 2) <> ""
        Set column = New columnInfo
        column.columnName = Cells(currentRow, 2)
        column.columnType = Cells(currentRow, 3)
        column.constraint = Cells(currentRow, 4)
        column.comment = Cells(currentRow, 5)
        columnArray.Add column
        currentRow = currentRow + 1
    Loop
    
    ' テンプレートエンジンに値を渡しはじめる
    Dim t As New MiniTemplator
    
    ' テンプレートファイル
    t.ReadTemplateFromFile ThisWorkbook.Path & "\" & templateFileName
    
    ' テーブル情報
    t.SetVariable "schema", table.schema
    t.SetVariable "tableName", table.tableName
    t.SetVariable "tableComment", table.comment
    
    ' カラム情報
    Dim columns As Integer
    For columns = 1 To columnArray.Count
        t.SetVariable "columnName", columnArray.Item(columns).columnName
        t.SetVariable "columnType", columnArray.Item(columns).columnType
        ' 最後のカラムにはカンマをつけない
        If columns = columnArray.Count Then
            t.SetVariable "constraint", columnArray.Item(columns).constraint
        Else
            t.SetVariable "constraint", columnArray.Item(columns).constraint & ","
        End If
        t.AddBlock "columns"
    Next
    
    'カラムのコメント情報
    For columns = 1 To columnArray.Count
        t.SetVariable "tableName", table.tableName
        t.SetVariable "columnName", columnArray.Item(columns).columnName
        t.SetVariable "columnComment", columnArray.Item(columns).comment
        t.AddBlock "comments"
    Next
    
    t.GenerateOutputToFile ThisWorkbook.Path & "\" & table.tableName & ".tbl"
    MsgBox "テーブル情報が次のファイルに書き込まれました : " & table.tableName & ".tbl"
    
End Sub

解説と言い訳

Private Const startRow = 5
Private Const startCol = 2

 上記の二行はカラムの定義がはじまる場所を示しているのですが、どう考えても美しくないです。

    ' テーブル情報を格納
    If Range("schema").Value <> "" Then
        table.schema = Range("schema").Value & "."
    Else
        table.schema = ""
    End If

 Rangeの中に名前らしきものが入ってて「?」と思われた方もいるかも知れませんが、セルには名前をつけることができます。Range("C1")みたいな指定も良いのですが、後から見たとき何だかなぁという気持ちになるので、できるだけ名前をつけたいところです。

    'カラム情報を格納
    Dim currentRow As Integer
    currentRow = startRow
    Do While Cells(currentRow, 2) <> ""
        Set column = New columnInfo
        column.columnName = Cells(currentRow, 2)
        column.columnType = Cells(currentRow, 3)
        column.constraint = Cells(currentRow, 4)
        column.comment = Cells(currentRow, 5)
        columnArray.Add column
        currentRow = currentRow + 1
    Loop

 インスタンスを作って必要な値を取得&格納し、コレクションクラスにぶち込むというループです。カラム名がないカラム定義はないと思うので、カラム名の記入がないと判断したところでループをストップさせます。

    ' テンプレートエンジンに値を渡しはじめる
    Dim t As New MiniTemplator

 テンプレートエンジンでファイル生成するためには、まずエンジンのインスタンスを生成します。

    ' テンプレートファイル
    t.ReadTemplateFromFile ThisWorkbook.Path & "\" & templateFileName

 テンプレートファイルを読み込みます。もちろんフルパスで指定。今回はExcelのカレントフォルダにファイルを配置しているので、ThisWorkbook.Pathでカレントフォルダのパスを取得しています。

 この際、テンプレート定義ファイルの構文がおかしかったりする場合はこの場でエラーが出ます。

    ' テーブル情報
    t.SetVariable "schema", table.schema
    t.SetVariable "tableName", table.tableName
    t.SetVariable "tableComment", table.comment

 SetVariableメソッドでどのタグにどんな値を割り当てるかを設定します。上記の例では${schema}にtable.schemaの内容を挿入したりしています。

    ' カラム情報
    Dim columns As Integer
    For columns = 1 To columnArray.Count
        t.SetVariable "columnName", columnArray.Item(columns).columnName
        t.SetVariable "columnType", columnArray.Item(columns).columnType
        ' 最後のカラムにはカンマをつけない
        If columns = columnArray.Count Then
            t.SetVariable "constraint", columnArray.Item(columns).constraint
        Else
            t.SetVariable "constraint", columnArray.Item(columns).constraint & ","
        End If
        t.AddBlock "columns"
    Next

 ブロックに対して値を割り当てています。一通り値を突っ込んだあと、AddBlockメソッドでどこのブロックに値を突っ込んでいるのかをエンジンに教えてあげます。

    t.GenerateOutputToFile ThisWorkbook.Path & "\" & table.tableName & ".tbl"
    MsgBox "テーブル情報が次のファイルに書き込まれました : " & table.tableName & ".tbl"

 そんな感じで値の挿入が終わったあとは、GenerateOutputToFileメソッドでファイルを出力してあげます。今回はテーブル名.tblというファイルを出力することにしました。

出力結果

 以下のような形で出力されます。

CREATE TABLE mah.cat_management
(
	cat_id	NUMBER	PRIMARY KEY,
	cat_name	VARCHAR2(50)	,
	cat_type	VARCHAR2(50)	,
	cat_condition	NUMBER(2)	
)
/
comment on table mah.cat_management is '猫の管理表'
/
comment on column mah.cat_management.cat_id is '猫のID'
/
comment on column mah.cat_management.cat_name is '猫の名前'
/
comment on column mah.cat_management.cat_type is '猫の種類'
/
comment on column mah.cat_management.cat_condition is '猫の状態'
/

考察

 MiniTemplatorのお手軽さは激しく評価できると思います。Excelの情報からちょっとしたファイルを出力したいケースは多々あると思いますので、VBAで使えるテンプレートエンジンがあるのは便利ですね。

 今回作成したマクロとExcelファイルは下記の場所に保存してあります。是非ご覧になってください。

 http://pre.mods.jp/strage/makesql.zip

*1:何故かソースコード中で勝手に小文字に変換され続けるので小文字のまま放っておいてしまいました。。なんでだろう、未だに謎