# 模板引擎

目前 Macaron 应用有两款官方模板引擎中间件可供选择，即 [`macaron.Renderer`](https://gowalker.org/gopkg.in/macaron.v1#Renderer) 和 [`pongo2.Pongoer`](https://gowalker.org/github.com/go-macaron/pongo2#Pongoer)。

您可以自由选择使用哪一款模板引擎，并且您只能为一个 [Macaron 实例](https://go-macaron.com/core_concepts#macaron-shi-li) 注册一款模板引擎。

共有特性：

* 均支持 XML、JSON 和原始数据格式的响应，它们之间的不同只体现在 HTML 渲染上。
* 均使用 `templates` 作为默认模板文件目录。
* 均使用 `.tmpl` 和 `.html` 作为默认模板文件后缀。
* 均支持通过 [Macaron 环境变量](https://go-macaron.com/core_concepts#macaron-huan-jing-bian-liang) 来判断是否缓存模板文件（当 `macaron.Env == macaron.PROD` 时）。

## 渲染 HTML

### Go 模板引擎

该服务可以通过函数 [`macaron.Renderer`](https://gowalker.org/gopkg.in/macaron.v1#Renderer) 来注入，并通过类型 [`macaron.Render`](https://gowalker.org/gopkg.in/macaron.v1#Render) 来体现。该服务为可选，一般情况下可直接使用 `*macaron.Context.Render`。该服务使用 Go 语言内置的模板引擎来渲染 HTML。如果想要了解更多有关使用方面的信息，请参见 [官方文档](https://gowalker.org/html/template)。

#### 使用示例

假设您的应用拥有以下目录结构：

```
main/
    |__ main.go
    |__ templates/
            |__ hello.tmpl
```

hello.tmpl：

```markup
<h1>Hello {{.Name}}</h1>
```

main.go：

```go
package main

import "gopkg.in/macaron.v1"

func main() {
    m := macaron.Classic()
    m.Use(macaron.Renderer())

    m.Get("/", func(ctx *macaron.Context) {
        ctx.Data["Name"] = "jeremy"
        ctx.HTML(200, "hello") // 200 为响应码
    })

    m.Run()
}
```

#### 自定义选项

该服务允许接受一个参数来进行自定义选项（[`macaron.RenderOptions`](https://gowalker.org/gopkg.in/macaron.v1#RenderOptions)）：

```go
package main

import "gopkg.in/macaron.v1"

func main() {
    m := macaron.Classic()
    m.Use(macaron.Renderer(macaron.RenderOptions{
        // 模板文件目录，默认为 "templates"
        Directory: "templates",
        // 模板文件后缀，默认为 [".tmpl", ".html"]
        Extensions: []string{".tmpl", ".html"},
        // 模板函数，默认为 []
        Funcs: []template.FuncMap{map[string]interface{}{
            "AppName": func() string {
                return "Macaron"
            },
            "AppVer": func() string {
                return "1.0.0"
            },
        }},
        // 模板语法分隔符，默认为 ["{{", "}}"]
        Delims: macaron.Delims{"{{", "}}"},
        // 追加的 Content-Type 头信息，默认为 "UTF-8"
        Charset: "UTF-8",
        // 渲染具有缩进格式的 JSON，默认为不缩进
        IndentJSON: true,
        // 渲染具有缩进格式的 XML，默认为不缩进
        IndentXML: true,
        // 渲染具有前缀的 JSON，默认为无前缀
        PrefixJSON: []byte("macaron"),
        // 渲染具有前缀的 XML，默认为无前缀
        PrefixXML: []byte("macaron"),
        // 允许输出格式为 XHTML 而不是 HTML，默认为 "text/html"
        HTMLContentType: "text/html",
    }))        
    // ...
}
```

### Pongo2 模板引擎

该服务可以通过函数 [`pongo2.Pongoer`](https://gowalker.org/github.com/go-macaron/pongo2#Pongoer) 来注入，并通过类型 [`macaron.Render`](https://gowalker.org/gopkg.in/macaron.v1#Render)来体现。该服务为可选，一般情况下可直接使用 `*macaron.Context.Render`。该服务使用 Pongo2 **v3** 模板引擎来渲染 HTML。如果想要了解更多有关使用方面的信息，请参见 [官方文档](https://github.com/flosch/pongo2)。

#### 使用示例

假设您的应用拥有以下目录结构：

```
main/
    |__ main.go
    |__ templates/
            |__ hello.tmpl
```

hello.tmpl：

```markup
<h1>Hello {{Name}}</h1>
```

main.go：

```go
package main

import (
    "github.com/go-macaron/pongo2"
    "gopkg.in/macaron.v1"
)

func main() {
    m := macaron.Classic()
    m.Use(pongo2.Pongoer())

    m.Get("/", func(ctx *macaron.Context) {
        ctx.Data["Name"] = "jeremy"
        ctx.HTML(200, "hello") // 200 is the response code.
    })

    m.Run()
}
```

#### 自定义选项

该服务允许接受一个参数来进行自定义选项（[`pongo2.Options`](https://gowalker.org/github.com/go-macaron/pongo2#Options)）：

```go
package main

import (
    "github.com/go-macaron/pongo2"
    "gopkg.in/macaron.v1"
)

func main() {
    m := macaron.Classic()
    m.Use(pongo2.Pongoer(pongo2.Options{
        // 模板文件目录，默认为 "templates"
        Directory: "templates",
        // 模板文件后缀，默认为 [".tmpl", ".html"]
        Extensions: []string{".tmpl", ".html"},
        // 追加的 Content-Type 头信息，默认为 "UTF-8"
        Charset: "UTF-8",
        // 渲染具有缩进格式的 JSON，默认为不缩进
        IndentJSON: true,
        // 渲染具有缩进格式的 XML，默认为不缩进
        IndentXML: true,
        // 允许输出格式为 XHTML 而不是 HTML，默认为 "text/html"
        HTMLContentType: "text/html",
    }))        
    // ...
}
```

### 模板集

当您的应用存在多套模板时，就需要使用模板集来实现运行时动态设置需要渲染的模板。

Go 模板引擎的使用方法：

```go
// ...
m.Use(macaron.Renderers(macaron.RenderOptions{
    Directory: "templates/default",
}, "theme1:templates/theme1", "theme2:templates/theme2"))

m.Get("/foobar", func(ctx *macaron.Context) {
    ctx.HTML(200, "hello")
})

m.Get("/foobar1", func(ctx *macaron.Context) {
    ctx.HTMLSet(200, "theme1", "hello")
})

m.Get("/foobar2", func(ctx *macaron.Context) {
    ctx.HTMLSet(200, "theme2", "hello")
})
// ...
```

Pongo2 模板引擎的使用方法：

```go
// ...
m.Use(pongo2.Pongoers(pongo2.Options{
    Directory: "templates/default",
}, "theme1:templates/theme1", "theme2:templates/theme2"))

m.Get("/foobar", func(ctx *macaron.Context) {
    ctx.HTML(200, "hello")
})

m.Get("/foobar1", func(ctx *macaron.Context) {
    ctx.HTMLSet(200, "theme1", "hello")
})

m.Get("/foobar2", func(ctx *macaron.Context) {
    ctx.HTMLSet(200, "theme2", "hello")
})
// ...
```

正如您所看到的那样，其实就是 2 个方法的不同：[`macaron.Renderers`](https://gowalker.org/gopkg.in/macaron.v1#Renderers) 和 [`pongo2.Pongoers`](https://gowalker.org/github.com/go-macaron/pongo2#Pongoers)。

第一个配置参数用于指定默认的模板集和配置选项，之后则是一个模板集名称和目录（通过 `:` 分隔）的列表。

如果您的模板集名称和模板集路径的最后一部分相同，则可以省略名称：

```go
// ...
m.Use(macaron.Renderers(RenderOptions{
    Directory: "templates/default",
}, "templates/theme1", "templates/theme2"))

m.Get("/foobar", func(ctx *macaron.Context) {
    ctx.HTML(200, "hello")
})

m.Get("/foobar1", func(ctx *macaron.Context) {
    ctx.HTMLSet(200, "theme1", "hello")
})

m.Get("/foobar2", func(ctx *macaron.Context) {
    ctx.HTMLSet(200, "theme2", "hello")
})
// ...
```

#### 模板集辅助方法

检查某个模板集是否存在：

```go
// ...
m.Get("/foobar", func(ctx *macaron.Context) {
    ok := ctx.HasTemplateSet("theme2")
    // ...
})
// ...
```

修改模板集的目录：

```go
// ...
m.Get("/foobar", func(ctx *macaron.Context) {
    ctx.SetTemplatePath("theme2", "templates/new/theme2")
    // ...
})
// ...
```

### 小结

也许您已经发现，除了在 HTML 语法上的不同之外，两款引擎在代码层面的用法是完全一样的。

如果您只是想要得到 HTML 渲染后的结果，则可以调用方法 `*macaron.Context.Render.HTMLString`：

```go
package main

import "gopkg.in/macaron.v1"

func main() {
    m := macaron.Classic()
    m.Use(macaron.Renderer())

    m.Get("/", func(ctx *macaron.Context) {
        ctx.Data["Name"] = "jeremy"
        output, err := ctx.HTMLString("hello")
        // 进行其它操作
    })

    m.Run()
}
```

## 渲染 XML、JSON 和原始数据

相对于渲染 HTML 而言，渲染 XML、JSON 和原始数据的工作要简单的多。

```go
package main

import "gopkg.in/macaron.v1"

type Person struct {
    Name string
    Age  int
    Sex  string
}

func main() {
    m := macaron.Classic()
    m.Use(macaron.Renderer())

    m.Get("/xml", func(ctx *macaron.Context) {
        p := Person{"Unknwon", 21, "male"}
        ctx.XML(200, &p)
    })
    m.Get("/json", func(ctx *macaron.Context) {
        p := Person{"Unknwon", 21, "male"}
        ctx.JSON(200, &p)
    })
    m.Get("/raw", func(ctx *macaron.Context) {
        ctx.RawData(200, []byte("raw data goes here"))
    })
    m.Get("/text", func(ctx *macaron.Context) {
        ctx.PlainText(200, []byte("plain text goes here"))
    })

    m.Run()
}
```

## 响应状态码、错误和重定向

如果您希望响应指定状态码、错误和重定向操作，则可以参照以下代码：

```go
package main

import "gopkg.in/macaron.v1"

func main() {
    m := macaron.Classic()
    m.Use(macaron.Renderer())

    m.Get("/status", func(ctx *macaron.Context) {
        ctx.Status(403)
    })
    m.Get("/error", func(ctx *macaron.Context) {
        ctx.Error(500, "Internal Server Error")
    })
    m.Get("/redirect", func(ctx *macaron.Context) {
        ctx.Redirect("/") // 第二个参数为响应码，默认为 302
    })

    m.Run()
}
```

## 运行时修改模板路径

如果您希望在运行时修改应用的模板路径，则可以调用方法 `*macaron.Context.SetTemplatePath`。需要注意的是，修改操作是全局生效的，而不只是针对当前请求。

### 使用示例

假设您的应用拥有以下目录结构：

```
main/
    |__ main.go
    |__ templates/
            |__ hello.tmpl
    |__ templates2/
            |__ hello.tmpl
```

templates/hello.tmpl：

```markup
<h1>Hello {{.Name}}</h1>
```

templates2/hello.tmpl：

```markup
<h1>What's up, {{.Name}}</h1>
```

main.go：

```go
package main

import "gopkg.in/macaron.v1"

func main() {
    m := macaron.Classic()
    m.Use(macaron.Renderer())

    m.Get("/old", func(ctx *macaron.Context) {
        ctx.Data["Name"] = "Unknwon"
        ctx.HTML(200, "hello")
        // 空字符串表示操作默认模板集
        ctx.SetTemplatePath("", "templates2")
    })
    m.Get("/new", func(ctx *macaron.Context) {
        ctx.Data["Name"] = "Unknwon"
        ctx.HTML(200, "hello")
    })

    m.Run()
}
```

当您首次请求 `/old` 页面时，响应结果为 `<h1>Hello Unknwon</h1>`，然后便执行了修改模板路径为 `template2`。此时，当您请求 `/new` 页面时，响应结果会变成 `<h1>What's up, Unknwon</h1>`。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://go-macaron.com/zh-cn/middlewares/templating.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
