Golang — template

Go language template engine

Go language has a built-in text template engine text/template and html/template for HTML documents.

Their mechanism of action can be briefly summarized as follows:

  • Template files are usually defined with .tmpl and .tpl as suffixes (other suffixes can also be used), and UTF8 encoding must be used.
  • Use { { and }} in the template file to wrap and identify the data that needs to be passed in.
  • The data passed to the template can be accessed by dot. If the data is a complex type of data, its fields can be accessed by { { .FieldName }}.
  • Except for the content wrapped by { { and }}, other content will be output without modification.

template syntax

The contents wrapped by { { and }} are collectively called action, which can be divided into two types:

  • data evaluations
  • control structures

The result of action evaluation will be directly copied to the template. The control structure is similar to that of our Go program, including conditional statements, loop statements, variables, function calls, etc...

1. Notes

{
    
    {
    
    /* a comment */}}
// 注释,执行时会忽略。可以多行。注释不能嵌套,并且必须紧贴分界符始止。

2. Remove spaces

Add a dash after the { { symbol and reserve one or more spaces to remove the blanks in front of it (including line breaks, tabs, spaces, etc.), that is, { {- xxxx .

Add one or more spaces and a dash - in front of }} to remove the space after it, ie xxxx -}}.

<p>{
   
   { 20 }} < {
   
   { 40 }}</p> // 20 < 40
<p>{
   
   { 20 -}} < {
   
   {- 40 }}</p> // 20<40

3. Pipeline pipeline

A pipeline is an operation that produces data. Such as { {.}}, { {.Name}}, funcname args, etc.

You can use the pipe symbol | to link multiple commands, the usage is similar to the pipe under unix: |The previous command passes the operation result (or return value) to the last position of the latter command.

{
   
   {"put" | printf "%s%s" "out" | printf "%q"}}  // "output"

4. Variables

When golang renders a template, it can accept a variable of type interface{}, and we can read the value in the variable in the template file and render it into the template.

The . between { { and }} represents the incoming variable (data), which represents the current object in the current scope, and the rendering of different variables (data) is different.

There are two commonly used types of incoming variables. One is a struct, and the fields of the struct (exposed properties) can be read in the template for rendering. There is also a map[string]interface{}, which can be used in the template to obtain the corresponding value for rendering.

Sample code:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
   <p>姓名:{
   
   { .Name }}</p> 
   <p>年龄:{
   
   { .Age }}</p>
   <p>性别:{
   
   { .Gender }}</p>
   <p>语文成绩:{
   
   { .Score.yuwen}}</p>
   <p>数学成绩:{
   
   { .Score.shuxue}}</p>
   <p>英语成绩:{
   
   { .Score.yingyu}}</p>
</body>
</html>
package main

import (
    "fmt"
    "html/template"
    "net/http"
)

// User 结构体
type User struct {
    
    
    Name   string
    Age    int
    Gender string
    Score  map[string]float64
}

func indexHandleFunc(w http.ResponseWriter, r *http.Request) {
    
    
    t, err := template.ParseFiles("./index.tmpl")
    if err != nil {
    
    
        fmt.Println("template parsefiles failed, err:", err)
        return
    }
    user := User{
    
    
        Name:   "ruby",
        Age:    20,
        Gender: "female",
        Score: map[string]float64{
    
    
            "yuwen":  98,
            "shuxue": 100,
            "yingyu": 94,
        },
    }
    t.Execute(w, user)
}

func main() {
    
    
    http.HandleFunc("/", indexHandleFunc)
    http.ListenAndServe(":8080", nil)
}

custom variable

{
    
    {
    
     $obj := "jack" }}
{
    
    {
    
     $obj }} // 输出:jack

5. Functions

Golang's templates actually have very limited functions, and many complex logics cannot be expressed directly using template syntax, so they can only be implemented using template functions.

First of all, when the template package creates a new template, it supports the .Funcs method to import a set of custom functions into the template, and subsequent files rendered through the template support calling these functions directly.

The function set is defined as:

type FuncMap map[string]interface{
    
    }

The key is the name of the method, and the value is the function. There is no limit to the number of parameters of this function, but there is a limit to the return value. There are two options, one is that there is only one return value, and the other is that there are two return values, but the second return value must be of type error. The difference between these two functions is that when the second function is called in the template, if the return of the second parameter of the template function is not empty, the rendering step will be interrupted and an error will be reported.

Built-in template functions:

var builtins = FuncMap{
    
    
    // 返回第一个为空的参数或最后一个参数。可以有任意多个参数。
    // "and x y"等价于"if x then y else x"
    "and": and,
    // 显式调用函数。第一个参数必须是函数类型,且不是template中的函数,而是外部函数。
    // 例如一个struct中的某个字段是func类型的。
    // "call .X.Y 1 2"表示调用dot.X.Y(1, 2),Y必须是func类型,函数参数是1和2。
    // 函数必须只能有一个或2个返回值,如果有第二个返回值,则必须为error类型。
    "call": call,
    // 返回与其参数的文本表示形式等效的转义HTML。
    // 这个函数在html/template中不可用。
    "html": HTMLEscaper,
    // 对可索引对象进行索引取值。第一个参数是索引对象,后面的参数是索引位。
    // "index x 1 2 3"代表的是x[1][2][3]。
    // 可索引对象包括map、slice、array。
    "index": index,
    // 返回与其参数的文本表示形式等效的转义JavaScript。
    "js": JSEscaper,
    // 返回参数的length。
    "len": length,
    // 布尔取反。只能一个参数。
    "not": not,
    // 返回第一个不为空的参数或最后一个参数。可以有任意多个参数。
    // "or x y"等价于"if x then x else y"。
    "or":      or,
    "print":   fmt.Sprint,
    "printf":  fmt.Sprintf,
    "println": fmt.Sprintln,
    // 以适合嵌入到网址查询中的形式返回其参数的文本表示的转义值。
    // 这个函数在html/template中不可用。
    "urlquery": URLQueryEscaper,
}

Comparison function:

eq arg1 arg2:
    arg1 == arg2时为true
ne arg1 arg2:
    arg1 != arg2时为true
lt arg1 arg2:
    arg1 < arg2时为true
le arg1 arg2:
    arg1 <= arg2时为true
gt arg1 arg2:
    arg1 > arg2时为true
ge arg1 arg2:
    arg1 >= arg2时为true

custom template function

t = t.Funcs(template.FuncMap{
    
    "handleFieldName": HandleFunc})

function call

{
    
    {
    
    funcname .arg1 .arg2}}

6. Condition judgment

{
    
    {
    
     if pipeline }} T1 {
    
    {
    
     end }}
{
    
    {
    
     if pipeline }} T1 {
    
    {
    
     else }} T2 {
    
    {
    
     end }}
{
    
    {
    
     if pipeline }} T1 {
    
    {
    
     else if pipeline }} T2 {
    
    {
    
     end }}

7. Loop through

{
    
    {
    
     range pipeline }} T1 {
    
    {
    
     end }}
// 如果 pipeline 的长度为 0 则输出 else 中的内容
{
    
    {
    
     range pipeline }} T1 {
    
    {
    
     else }} T2 {
    
    {
    
     end }}

range can traverse slice, array, map or channel. When traversing, it will be set to the element currently being traversed.

For the first expression, when the value of the traversal object is 0, the range skips directly, just like if. For the second expression, execute else when traversing to a value of 0.

The parameter part of range is pipeline, so it can be assigned during the iteration process. But there are two assignment cases:

{
    
    {
    
     range $value := pipeline }} T1 {
    
    {
    
     end }}
{
    
    {
    
     range $key, $value := pipeline }} T1 {
    
    {
    
     end }}

If only one variable is assigned in the range, this variable is the value of the element currently being traversed. If assigned to two variables, the first variable is the index value (array/slice is the value, map is the key), and the second variable is the value of the element currently being traversed.

8. with…end

{
    
    {
    
     with pipeline }} T1 {
    
    {
    
     end }}
{
    
    {
    
     with pipeline }} T1 {
    
    {
    
     else }} T0 {
    
    {
    
     end }}

For the first format, when the pipeline is not 0, set . as the value of the pipeline operation, otherwise skip.
For the second format, when the value of the pipeline is 0, execute the else statement block T0, otherwise, set it to the value of the pipeline operation, and execute T1.

9. Template nesting

  • define
    define can directly define a template in the content to be parsed

    // 定义名称为name的template
    {
          
          {
          
           define "name" }} T {
          
          {
          
           end }}
    
  • template
    uses template to execute the template

    // 执行名为name的template
    {
          
          {
          
           template "name" }}
    {
          
          {
          
           template "name"  pipeline }}
    
  • block

    {
          
          {
          
           block "name" pipeline }} T {
          
          {
          
           end }}
    

    block is equivalent to define to define a template named name, and execute this template where "necessary", and set . as the value of pipeline during execution.

    It is equivalent to: first { { define “name” }} T { { end }} and then execute { { template “name” pipeline }}.

Code example:

<!-- index.tmpl -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
   {
   
   { template "content"}} 
</body>
</html>
<!-- red.tmpl -->
{
   
   { define "content" }} 
    <div style="color:red"><h3>hello world</h3></div>
{
   
   { end }}
<!-- blue.tmpl -->
{
   
   { define "content" }} 
    <div style="color:blue"><h3>hello world</h3></div>
{
   
   { end }}
// main.go
package main

import (
    "html/template"
    "math/rand"
    "net/http"
    "time"
)

func indexHandleFunc(w http.ResponseWriter, r *http.Request) {
    
    
    t := template.New("index.tmpl")
    rand.Seed(time.Now().UnixNano())
    if rand.Intn(100) > 50 {
    
    
        t, _ = template.ParseFiles("./index.tmpl", "./red.tmpl")
    } else {
    
    
        t, _ = template.ParseFiles("./index.tmpl", "./blue.tmpl")
    }
    t.Execute(w, "")
}
func main() {
    
    
    http.HandleFunc("/", indexHandleFunc)
    http.ListenAndServe(":8080", nil)
}

If you use block, you can set the default content template.
Modify index.tmpl

<!-- index.tmpl -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    {
   
   { block "content" . }}
    <div style="color:yellow"><h3>hello world</h3></div>
    {
   
   { end }}
</body>
</html>

Modify the backend program:

// main.go
package main

import (
    "html/template"
    "math/rand"
    "net/http"
    "time"
)

func indexHandleFunc(w http.ResponseWriter, r *http.Request) {
    
    
    t := template.New("index.tmpl")
    rand.Seed(time.Now().UnixNano())
    if rand.Intn(100) > 75 {
    
    
        t, _ = template.ParseFiles("./index.tmpl", "./red.tmpl")
    } else if rand.Intn(100) > 25 {
    
    
        t, _ = template.ParseFiles("./index.tmpl", "./blue.tmpl")
    } else {
    
    
        t, _ = template.ParseFiles("./index.tmpl")
    }
    t.Execute(w, "")
}
func main() {
    
    
    http.HandleFunc("/", indexHandleFunc)
    http.ListenAndServe(":8080", nil)
}

10. Template Inheritance

Template inheritance is realized through block, define, and template.

Sample code:

<!-- base.tmpl -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .head {
      
      
            height: 50px;
            background-color: red;
            width: 100%;
            text-align: center;
        }
        .main {
      
      
            width: 100%;
        }
        .main .left {
      
      
            width: 30%;
            height: 1000px;
            float: left;
            background-color:violet;
            text-align: center;
        }
        .main .right {
      
      
            width: 70%;
            float: left;
            text-align: center;
            height: 1000px;
            background-color:yellowgreen;
        }
    </style>
</head>
<body>
    <div class="head">
        <h1>head</h1>
    </div> 
    <div class="main">
        <div class="left">
        <h1>side</h1>
        </div>
        <div class="right">
            {
   
   { block "content" . }}
            <h1>content</h1>
            {
   
   { end }}
        </div>
    </div>
</body>
</html>
<!-- index.tmpl -->
{
   
   { template "base.tmpl" . }}

{
   
   { define "content" }}
<h1>这是index页面</h1>
{
   
   { . }}
{
   
   { end }}
<!-- home.tmpl -->
{
   
   { template "base.tmpl" . }}

{
   
   { define "content" }}
<h1>这是home页面</h1>
{
   
   { . }}
{
   
   { end }}
// main.go
package main

import (
    "html/template"
    "net/http"
)

func indexHandleFunc(w http.ResponseWriter, r *http.Request) {
    
    
    t := template.New("index.tmpl")
    t, _ = t.ParseFiles("./base.tmpl", "./index.tmpl")
    t.Execute(w, "index")
}

func homeHandleFunc(w http.ResponseWriter, r *http.Request) {
    
    
    t := template.New("home.tmpl")
    t, _ = t.ParseFiles("./base.tmpl", "./home.tmpl")
    t.Execute(w, "home")
}

func main() {
    
    
    server := http.Server{
    
    
        Addr: "localhost:8080",
    }
    http.HandleFunc("/index", indexHandleFunc)
    http.HandleFunc("/home", homeHandleFunc)
    server.ListenAndServe()
}

11. Modify the default identifier

The template engine of the Go standard library uses curly braces { { and }} as identifiers, and many front-end frameworks (such as Vue and AngularJS) also use { { and }} as identifiers, so when we use the Go language template engine and the above There will be conflicts in the front-end framework. At this time, we need to modify the identifier, modify the front-end or modify the Go language. Here's how to modify the default identifiers of the Go language template engine:

template.New("test").Delims("{[", "]}").ParseFiles("./t.tmpl")

12. Context awareness of html/template

For the html/template package, there is a useful feature: context awareness. text/template does not have this function.

Context awareness specifically refers to automatically escaping in different formats according to the environment css, js, html, url path, and url query.

sample code

<!-- index.tmpl -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div>{
   
   { . }}</div>
</body>
</html>
// main.go
package main

import (
    // "text/template"
    "html/template"
    "net/http"
)

func indexHandleFunc(w http.ResponseWriter, r *http.Request) {
    
    
    t, _ := template.ParseFiles("./index.tmpl")
    data := `<script>alert("helloworld")</script>`
    t.Execute(w, data)
}

func main() {
    
    
    http.HandleFunc("/", indexHandleFunc)
    http.ListenAndServe(":8080", nil)
}

Run the program, the page displays:

<script>alert("helloworld")</script>

don't escape

Context-aware automatic escaping can make the program more secure, such as preventing XSS attacks (for example, entering the content in the form and submitting it will cause the part of the script submitted by the user to be executed).

If you really don't want to escape, you can perform type conversion.

type CSS
type HTML
type JS
type URL

Write a custom template function to implement the type conversion of the content.

Sample code:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div>{
   
   { .str1 }}</div>
    <div>{
   
   { .str2 | safe }}</div>
</body>
</html>
package main

import (
    // "text/template"
    "html/template"
    "net/http"
)

func indexHandleFunc(w http.ResponseWriter, r *http.Request) {
    
    
    t := template.New("index.tmpl")
    t.Funcs(template.FuncMap{
    
    
        "safe": func(str string) template.HTML {
    
    
            return template.HTML(str)
        },
    }).ParseFiles("./index.tmpl")
    m := map[string]interface{
    
    }{
    
    
        "str1": `<script>alert("helloworld")</script>`,
        "str2": `<a href = "http://baidu.com">baidu</a>`,
    }
    t.Execute(w, m)
}

Reference document:
[go language learning] template of standard library

Guess you like

Origin blog.csdn.net/weixin_45804031/article/details/125511755