月別アーカイブ: 2012年4月

Go言語:templateパッケージでHTML等テキストファイルを読み込む(応用編2)

templateパッケージでHTML等の任意のテキストファイルを読み込み、出力する方法を説明する。
応用編その2ではテンプレート内に埋め込む「アクション」で利用できる様々な機能について解説する。
なお、APIバージョンGo1で説明する。
バージョン3ではパッケージ名、関数名が異なるので注意。

概要

templateパッケージでは、任意のテキストを読み込み、テキストに埋め込まれたタグに対してデータの評価や制御(アクションという)をおこなって出力する。
templateパッケージ応用編1では、アクションの基本的な機能について説明したが、今回は以下のようなさらに高度な機能について説明する。

  • {{if}}アクションにnot、and、or評価をおこなう。
  • 配列、Slice、Mapのサイズや任意のインデックスの値を取得する。
  • 出力結果のフォーマットをおこなう。
  • 任意の演算をおこなった結果を出力する。

not演算によりbool値を評価する

package main

import (
	"fmt"
	"os"
	"text/template"
)

const templateString string = `
{{if not .}}TRUE {{/* not演算をおこなう */}}
{{else}}FALSE
{{end}}
`

func main() {
	var t = template.Must(template.New("text").Parse(templateString))

	if err := t.Execute(os.Stdout, true); err != nil { // trueを渡す
		fmt.Println(err.Error())
	}
}

実行結果

FALSE

and, or演算によりbool値を評価する

package main

import (
	"fmt"
	"os"
	"text/template"
)

const templateString string = `
{{with .}}
    {{if and .Field1 .Field2}}TRUE {{/* and演算をおこなう */}}
    {{else}}FALSE
    {{end}}
    {{if or .Field1 .Field2}}TRUE {{/* or演算をおこなう */}}
    {{else}}FALSE
    {{end}}
{{end}}
`

type Fields struct {
    Field1 bool
    Field2 bool
}

func main() {
    f := Fields{true, false}

	var t = template.Must(template.New("text").Parse(templateString))

	if err := t.Execute(os.Stdout, f); err != nil { // 構造体Fieldsを渡す
		fmt.Println(err.Error())
	}
}

実行結果

FALSE
    
TRUE

配列のサイズや任意のインデックスの値を出力する

package main

import (
	"fmt"
	"os"
	"text/template"
)

const templateString string = `
{{len .}} {{/* 配列のサイズを出力する */}}
{{index . 1}} {{/* 配列の[1]の値を出力する */}}
`

func main() {
    array := []int{1, 2, 4, 8}

	var t = template.Must(template.New("text").Parse(templateString))

	if err := t.Execute(os.Stdout, array); err != nil { // 配列を渡す
		fmt.Println(err.Error())
	}
}

実行結果

4
2

変数に一度値を格納し、出力する

package main

import (
	"fmt"
	"os"
	"text/template"
)

const templateString string = `
{{range $i, $v := .}}{{$i}}:{{$v}}
{{end}}
`

func main() {
    array := []int{1, 2, 4, 8}

	var t = template.Must(template.New("text").Parse(templateString))

	if err := t.Execute(os.Stdout, array); err != nil { // 配列を渡す
		fmt.Println(err.Error())
	}
}

実行結果

0:1
1:2
2:4
3:8

値のフォーマットを変更して出力する

package main

import (
	"fmt"
	"os"
	"text/template"
)

const templateString string = `
{{.Tags}}

{{html .Tags}} {{/* <や"などhtmlタグに用いる文字列をエスケープする */}}
{{.Tags | html}}

{{printf "%c" .Char}} {{/* 文字型で出力する */}}
{{.Char | printf "%c"}}
`

type Fields struct {
    Tags string
    Char int
}

func main() {
    f := Fields{"<a href="aaa">link</a>", 65}

	var t = template.Must(template.New("text").Parse(templateString))

	if err := t.Execute(os.Stdout, f); err != nil { // 構造体Fieldsを渡す
		fmt.Println(err.Error())
	}
}

実行結果


<a href="aaa">link</a>

&lt;a href=&#34;aaa&#34;&gt;link&lt;/a&gt;
&lt;a href=&#34;aaa&#34;&gt;link&lt;/a&gt;

A
A

構造体に定義された関数を呼び出した結果を利用する

package main

import (
	"fmt"
	"os"
	"text/template"
)

const templateString string = `
{{if call .F .Field}}TRUE {{/* 構造体に定義されたF関数をFieldフィールドを引数として呼び出す */}}
{{else}}FALSE
{{end}}
`

type Fields struct {
	Field int
    F func(n int) bool 
}

func validate(n int) bool {
    if n > 0 {
		return true
	}
	return false
}

func main() {
    f := Fields{-10, validate}

	var t = template.Must(template.New("text").Parse(templateString))

	if err := t.Execute(os.Stdout, f); err != nil { // 構造体Fieldsを渡す
		fmt.Println(err.Error())
	}
}

実行結果

FALSE

解説

変数

{{.}}{{range .}} {{.}} {{end}}のように直接値を出力するだけでなく、一度変数に格納した値を出力することができる。
任意の値を変数に格納するには以下のように記述する。

$変数名 := 値

配列の値だけでなく、インデックスを一緒に出力したい場合などに使用する。

{{range $i, $x := .}} {{$i}}:{{$x}} {{end}}

関数

  • bool値の評価にnot, and, orを利用できる。
  • not 引数

    引数のbool値をnot演算した結果を返す。

    and 引数1 引数2

    引数1引数2のbool値をand演算した結果を返す。

    or 引数1 引数2

    引数1引数2のbool値をor演算した結果を返す。

  • 配列、Slice、Mapのサイズ取得にlen任意のインデックスの値の取得にindexを利用できる。
  • len 引数

    引数のサイズを返す。

    index 引数1 引数2

    引数1のインデックス[引数2]の値を返す。

  • <や"などhtmlタグに用いる文字列をエスケープするためにhtmlを利用できる。
  • html 引数
    値 | html

    どちらも出力結果は同じ。
    |により、|の左側で評価された値が右側の関数の第2引数として用いられる。

  • fmtパッケージによるフォーマット変更にprint, printf, printlnを利用できる。
  • print 引数1 引数2 ...
    printf 引数1 引数2 ...
    println 引数1 引数2 ...
    値 | print 引数1

    fmtパッケージの各Print系関数を利用する。
    引数1にはフォーマット、引数2以降には出力対象の値を指定する。
    使用できるフォーマットはfmtパッケージの各Print系関数と同じ。
    例として以下のように使用する。

    {{printf "%c, %d" .Var1 .Var2}}

  • 構造体や変数に定義された関数を呼び出すためにcallを利用できる。
  • templateではアクションタグ内で四則演算等、何らかのロジックを実行した結果を出力することができない。
    代わりに、templateに渡す変数や構造体に定義した関数を呼び出しその結果を用いることができる。
    以下のようにアクションタグを指定する。

    call 関数名 (引数1) ...

    関数名で指定した関数を引数1に続く引数で呼び出す。
    引数がない場合は引数1以降の部分は省略する。
    変数や構造体には以下のように関数を定義する。

    type FuncStruct struct {
        F func(n int) string
    }
    
    func doSomething(n int) string {
        // do something
    }
    
    func main() {
        s := FuncStruct{doSomething}
        ...
    }
    

    この例で関数doSomethingを呼び出した結果を出力するには以下のようにアクションタグを指定する。

    {{call .F 100}}

    Go言語:templateパッケージでHTML等テキストファイルを読み込む(応用編1)

    templateパッケージでHTML等の任意のテキストファイルを読み込み、出力する方法を説明する。
    応用編ではテンプレート内に埋め込む「アクション」について解説する。
    なお、APIバージョンGo1で説明する。
    バージョン3ではパッケージ名、関数名が異なるので注意。

    概要

    templateパッケージでは、任意のテキストを読み込み、テキストに埋め込まれたタグに対してデータの評価や制御(アクションという)をおこなって出力する。いくつかのアクションの指定により、以下のようなことができる。

    • テンプレートに渡された引数や構造体のフィールドを出力する。
    • テンプレートに渡されたbool値を評価し、出力を制御する。
    • テンプレートに渡された配列、Slice、Mapのデータを繰り返し出力する。

    配列の要素をすべて表示するサンプル

    package main
    
    import (
    	"fmt"
    	"os"
    	"text/template"
    )
    
    const templateString string = `テンプレートの始め
    {{range .}}
    繰り返し処理 {{.}}
    {{end}}
    テンプレートの終わり
    `
    
    func main() {
    	var stringArray []string = []string{"Hello", "World", "!!!!"}
    	var t = template.Must(template.New("html").Parse(templateString))
    
    	if err := t.Execute(os.Stdout, stringArray); err != nil { // 配列を渡す 
    		fmt.Println(err.Error())
    	}
    }
    

    出力結果

    テンプレートの始め
    
    繰り返し処理 Hello
    
    繰り返し処理 World
    
    繰り返し処理 !!!!
    
    テンプレートの終わり
    

    構造体、Mapの要素の表示、if文による条件分岐を用いたサンプル

    package main
    
    import (
    	"fmt"
    	"os"
    	"text/template"
    )
    
    type Struct struct {
        SampleString string
        SampleMap map[string]MapValue
        SampleBool bool
    }
    
    type MapValue struct {
        IntValue int
        StringValue string
    }
    
    const templateString string = `テンプレートの始め
    {{with .}}
        Structが空でなければこの先を出力する
        {{.SampleString}}:フィールドの値を出力
        {{range .SampleMap}}
            Mapの繰り返し処理 {{.IntValue}}: {{.StringValue}}
        {{end}}
        {{if .SampleBool}}
            bool変数がtrueならば出力
        {{else}}
            bool変数がfalseならば出力
        {{end}}
    {{end}}
    テンプレートの終わり
    `
    
    func main() {
        values := Struct{"サンプルテキスト", make(map[string]MapValue), false}
        values.SampleMap["first"] = MapValue{1, "Hello"}
        values.SampleMap["second"] = MapValue{2, "World"}
        values.SampleMap["third"] = MapValue{3, "!!!!"}
    	var t = template.Must(template.New("text").Parse(templateString))
    
    	if err := t.Execute(os.Stdout, values); err != nil { // 構造体を渡す 
    		fmt.Println(err.Error())
    	}
    }
    

    出力結果

    テンプレートの始め
    
        Structが空でなければこの先を出力する
        サンプルテキスト:フィールドの値を出力
        
            Mapの繰り返し処理 1: Hello
        
            Mapの繰り返し処理 2: World
        
            Mapの繰り返し処理 3: !!!!
        
        
            bool変数がfalseならば出力
        
    
    テンプレートの終わり
    

    解説

    アクションタグの種類

    {{.}}

    テンプレートに渡された引数を出力する。

    {{.Member}}

    テンプレートに渡された引数が構造体の場合、Memberフィールドの値を出力する。

    {{if .}} 分岐1 {{else}} 分岐2 {{end}}

    テンプレートに渡された引数のbool値がtrueの場合「分岐1」が出力され、falseの場合「分岐2」が出力される。{{else}}は省略できる。

    {{range .}} {{.}} {{else}} なし {{end}}

    テンプレートに渡された引数が配列、Slice、Mapの場合、lengthが1以上の場合はすべての要素を表示し({{.}}を要素数分繰り返し)、lengthが0の場合には「なし」が表示される。{{else}}は省略できる。

    {{with .}} 分岐1 {{else}} 分岐2 {{end}}

    テンプレートに渡された引数の値が空でない場合「分岐1」が出力され、空の場合「分岐2」が出力される。{{else}}は省略できる。

    応用

    テンプレートを実際のWebアプリケーションに用いる例を紹介する。
    サンプルとしてLegacy-BBSを参照してもらいたい。
    このサンプルは、下記の2つの部分に分かれている。

    • メッセージを投稿するためのフォーム部分
    • 投稿時に名前やメッセージが空白でないかのバリデーションを行い、エラーメッセージを表示する。
      バリデーションの結果を{{if}}で判定し、エラーの表示をおこなう。

    • 投稿されたメッセージを表示する部分
    • データストアに格納されている複数のメッセージを表示する。
      メッセージは構造体Entry_viewの配列に格納し、{{range}}により表示をおこなう。

    構造体とテンプレートのサンプルは下記の通り。
    なお、この例にはこれらの構造体のデータを取得・出力するロジックは省略している。
    ロジックについてはまた別の機会に説明する。

    type View struct {
    	Entries      []Entry_view
    	Errors       Validation
    }
    
    type Entry_view struct {
    	Name    string
    	Email   string
    	Title   string
    	Message string
    	Date    string
    }
    
    type Validation struct {
    	NameError    bool
    	MessageError bool
    }
    
    <html>
    <body>
        <p>Legacy BBS Sample</p>
        {{/* メッセージを投稿するためのフォーム部分 */}}
        <form method="POST" action="">
            {{with .Errors}}
                Submitter
                <input type="text" name="name" /><br />
                {{if .NameError}}<font color="red">Enter a submitter</font><br />{{end}}
                Mail
                <input type="text" name="email" /><br />
                Title
                <input type="text" name="title" /><br />
                Message
                <textarea name="message"></textarea><br />
                {{if .MessageError}}<font color="red">Enter a message</font><br />{{end}}
                <input type="submit" name="submit" value="Submit" />
            {{end}}
        </form>
    
        {{/* 投稿されたメッセージを表示する部分 */}}
        {{range .Entries}}
            <hr />
            <p>
                {{.Title}}  Submitter:
                {{if .Email}}<a href="mailto:{{.Email}}">{{.Name}}</a>
                {{else}}{{.Name}}
                {{end}}
                Date: {{.Date}}
            </p>
            <p>{{.Message}}</p>
        {{end}}
    </body>
    </html>
    

    応用編2ではアクションタグに使用できる関数や変数を説明する。