html空格占位符-去每日图书馆快速模板

2023-09-03 0 2,200 百度已收录

介绍

fasttemplate是一个比较简单易用的大型模板库。 fasttemplate的作者valyala也开源了很多优秀的库,比如大名鼎鼎的fasthttp、前面介绍的bytebufferpool、以及重量级模板库quicktemplate。 Quicktemplate 比标准库中的 text/template 和 html/template 更加灵活且易于使用。 稍后会具体介绍。 今天我要介绍的fasttemlate只关注一个很小的领域——字符串替换。 它的目标是替换strings.Replace、fmt.Sprintf等,并提供一种简单、易用、高性能的字符串替换方法。

本文首先介绍fasttemplate的用法,然后看一下源码实现的一些细节。

快速使用

本文中的代码使用 Go Modules。

创建目录并初始化它:

$ mkdir fasttemplate && cd fasttemplate
$ go mod init github.com/darjun/go-daily-lib/fasttemplate

安装fasttemplate库:

$ go get -u github.com/valyala/fasttemplate

编写代码:

package main

import (
  "fmt"

  "github.com/valyala/fasttemplate"
)

func main() {
  template := `name: {{name}}
age: {{age}}`

  t := fasttemplate.New(template, "{{", "}}")
  s1 := t.ExecuteString(map[string]interface{}{
    "name""dj",
    "age":  "18",
  })
  s2 := t.ExecuteString(map[string]interface{}{
    "name""hjw",
    "age":  "20",
  })
  fmt.Println(s1)
  fmt.Println(s2)
}

运行结果:

name: dj
age: 18

我们可以自定义占位符,分别使用 {{ 和 }} 作为开始和结束占位符。 我们可以将其替换为[[和]],只需简单更改代码即可:

template := `name: [[name]]
age: [[age]]`

t := fasttemplate.New(template, "[[""]]")

另外,需要注意的是,传入参数的类型为map[string]interface{}html空格占位符,但fasttemplate只接受[]byte、string和TagFunc类型的值。 这也是为什么里面的18要用双冒号括起来的原因。

另一点需要注意的是 fasttemplate.New() 返回一个模板对象。 如果模板解析失败html空格占位符,会直接panic。 如果你想自己处理错误,可以调用 fasttemplate.NewTemplate() 方法,该方法返回一个模板对象和一个错误。 事实上,fasttemplate.New()内部调用了fasttemplate.NewTemplate()。 如果返回错误,则恐慌:

// src/github.com/valyala/fasttemplate/template.go
func New(template, startTag, endTag string) *Template {
  t, err := NewTemplate(template, startTag, endTag)
  if err != nil {
    panic(err)
  }
  return t
}

func NewTemplate(template, startTag, endTag string) (*Template, error) {
  var t Template
  err := t.Reset(template, startTag, endTag)
  if err != nil {
    return nil, err
  }
  return &t, nil
}

这也可能是一种常见用法。 例如,不想处理错误的程序,有时可以选择直接恐慌。 例如html.template标准库还提供了Must()方法,一般都是这种方式使用,解析失败时会panic:

t := template.Must(template.New("name").Parse("html"))

不要在占位符内添加空格! ! !

不要在占位符内添加空格! ! !

不要在占位符内添加空格! ! !

捷径法

使用fasttemplate.New()方法定义一个模板对象,我们可以使用不同的参数来多次替换它。 然而,有时我们会进行大量的一次性替换,每次都定义模板对象会变得很乏味。 fasttemplate还提供了一次性替换的方法:

func main() {
  template := `name: [name]
age: [age]`

  s := fasttemplate.ExecuteString(template, "[""]"map[string]interface{}{
    "name""dj",
    "age":  "18",
  })
  fmt.Println(s)
}

使用这些方法,我们需要同时传入模板字符串、开始占位符、结束占位符和替换参数。

标签函数

fasttemplate提供了一个TagFunc,可以减少一些替换的逻辑。 TagFunc 是一个函数:

type TagFunc func(w io.Writer, tag string) (int, error)

在执行替换时,fasttemplate会对每个占位符调用一次TagFunc函数,tag是占位符的名称。 请参阅以下过程:

func main() {
  template := `name: {{name}}
age: {{age}}`

  t := fasttemplate.New(template, "{{", "}}")
  s := t.ExecuteFuncString(func(w io.Writer, tag string) (int, error) {
    switch tag {
    case "name":
      return w.Write([]byte("dj"))
    case "age":
      return w.Write([]byte("18"))
    default:
      return 0nil
    }
  })

  fmt.Println(s)
}

这似乎是入门示例程序的TagFunc版本,根据传入的标签写入不同的值。如果我们查看源代码,我们会发现ExecuteString()最终会调用ExecuteFuncString()。 fasttemplate提供了标准的TagFunc:

func (t *Template) ExecuteString(m map[string]interface{}) string {
  return t.ExecuteFuncString(func(w io.Writer, tag string) (int, error) { return stdTagFunc(w, tag, m) })
}

func stdTagFunc(w io.Writer, tag string, m map[string]interface{}) (int, error) {
  v := m[tag]
  if v == nil {
    return 0nil
  }
  switch value := v.(type) {
  case []byte:
    return w.Write(value)
  case string:
    return w.Write([]byte(value))
  case TagFunc:
    return value(w, tag)
  default:
    panic(fmt.Sprintf("tag=%q contains unexpected value type=%#v. Expected []byte, string or TagFunc", tag, v))
  }
}

标准TagFunc的实现也很简单,就是从参数map[string]interface{}中取出对应的值进行相应的处理,如果是[]byte和string类型,则直接调用io的写入方法。作家。 如果是TagFunc类型,则直接调用该方法,传入io.Writer和tag。 其他类型直接恐慌并抛出错误。

如果参数map[string]interface{}中不存在模板中的标签,有两种处理方式:

keepUnknownTagFunc代码如下:

func keepUnknownTagFunc(w io.Writer, startTag, endTag, tag string, m map[string]interface{}) (int, error) {
  v, ok := m[tag]
  if !ok {
    if _, err := w.Write(unsafeString2Bytes(startTag)); err != nil {
      return 0, err
    }
    if _, err := w.Write(unsafeString2Bytes(tag)); err != nil {
      return 0, err
    }
    if _, err := w.Write(unsafeString2Bytes(endTag)); err != nil {
      return 0, err
    }
    return len(startTag) + len(tag) + len(endTag), nil
  }
  if v == nil {
    return 0nil
  }
  switch value := v.(type) {
  case []byte:
    return w.Write(value)
  case string:
    return w.Write([]byte(value))
  case TagFunc:
    return value(w, tag)
  default:
    panic(fmt.Sprintf("tag=%q contains unexpected value type=%#v. Expected []byte, string or TagFunc", tag, v))
  }
}

如果在函数前半部分没有找到标签,则后半部分的处理与 stdTagFunc 相同。 直接写入startTag + tag + endTag作为替换值。

上面我们调用的ExecuteString()方法使用了stdTagFunc,它直接用空字符串替换无法识别的标签。 如果您想保留无法识别的标签,只需调用 ExecuteStringStd() 方法即可。 该方法遇到无法识别的标签时,会被保留:

func main() {
  template := `name: {{name}}
age: {{age}}`

  t := fasttemplate.New(template, "{{", "}}")
  m := map[string]interface{}{"name""dj"}
  s1 := t.ExecuteString(m)
  fmt.Println(s1)

  s2 := t.ExecuteStringStd(m)
  fmt.Println(s2)
}

参数中没有年龄,运行结果为:

name: dj
age:
name: dj
age: {{age}}

带 io.Writer 参数的方法

上面描述的方法都在末尾返回一个字符串。 方法名称中包含字符串:ExecuteString()/ExecuteFuncString()。

我们可以直接传入一个io.Writer参数,调用该参数的Write()方法直接写入结果字符串。 这种类型的方法名中没有String:Execute()/ExecuteFunc():

func main() {
  template := `name: {{name}}
age: {{age}}`

  t := fasttemplate.New(template, "{{", "}}")
  t.Execute(os.Stdout, map[string]interface{}{
    "name""dj",
    "age":  "18",
  })

  fmt.Println()

  t.ExecuteFunc(os.Stdout, func(w io.Writer, tag string) (int, error) {
    switch tag {
    case "name":
      return w.Write([]byte("hjw"))
    case "age":
      return w.Write([]byte("20"))
    }

    return 0nil
  })
}

由于os.Stdout实现了io.Writer套接字,因此可以直接传入。 结果直接传递给 os.Stdout。 跑步:

name: dj
age: 18
name: hjw
age: 20

源码分析

首先我们看一下模板对象的结构和创建:

// src/github.com/valyala/fasttemplate/template.go
type Template struct {
  template string
  startTag string
  endTag   string

  texts          [][]byte
  tags           []string
  byteBufferPool bytebufferpool.Pool
}

func NewTemplate(template, startTag, endTag string) (*Template, error) {
  var t Template
  err := t.Reset(template, startTag, endTag)
  if err != nil {
    return nil, err
  }
  return &t, nil
}

模板创建完成后,会调用Reset()方法进行初始化:

func (t *Template) Reset(template, startTag, endTag string) error {
  t.template = template
  t.startTag = startTag
  t.endTag = endTag
  t.texts = t.texts[:0]
  t.tags = t.tags[:0]

  if len(startTag) == 0 {
    panic("startTag cannot be empty")
  }
  if len(endTag) == 0 {
    panic("endTag cannot be empty")
  }

  s := unsafeString2Bytes(template)
  a := unsafeString2Bytes(startTag)
  b := unsafeString2Bytes(endTag)

  tagsCount := bytes.Count(s, a)
  if tagsCount == 0 {
    return nil
  }

  if tagsCount+1 > cap(t.texts) {
    t.texts = make([][]byte0, tagsCount+1)
  }
  if tagsCount > cap(t.tags) {
    t.tags = make([]string0, tagsCount)
  }

  for {
    n := bytes.Index(s, a)
    if n < 0 {
      t.texts = append(t.texts, s)
      break
    }
    t.texts = append(t.texts, s[:n])

    s = s[n+len(a):]
    n = bytes.Index(s, b)
    if n < 0 {
      return fmt.Errorf("Cannot find end tag=%q in the template=%q starting from %q", endTag, template, s)
    }

    t.tags = append(t.tags, unsafeBytes2String(s[:n]))
    s = s[n+len(b):]
  }

  return nil
}

初始化执行以下操作:

代码详情:

看介绍,好像有很多技巧。 其实核心方法就是ExecuteFunc()。 所有其他方式都是直接或间接调用它:

// src/github.com/valyala/fasttemplate/template.go
func (t *Template) Execute(w io.Writer, m map[string]interface{}) (int64, error) {
  return t.ExecuteFunc(w, func(w io.Writer, tag string) (int, error) { return stdTagFunc(w, tag, m) })
}

func (t *Template) ExecuteStd(w io.Writer, m map[string]interface{}) (int64, error) {
  return t.ExecuteFunc(w, func(w io.Writer, tag string) (int, error) { return keepUnknownTagFunc(w, t.startTag, t.endTag, tag, m) })
}

func (t *Template) ExecuteFuncString(f TagFunc) string {
  s, err := t.ExecuteFuncStringWithErr(f)
  if err != nil {
    panic(fmt.Sprintf("unexpected error: %s", err))
  }
  return s
}

func (t *Template) ExecuteFuncStringWithErr(f TagFunc) (string, error) {
  bb := t.byteBufferPool.Get()
  if _, err := t.ExecuteFunc(bb, f); err != nil {
    bb.Reset()
    t.byteBufferPool.Put(bb)
    return "", err
  }
  s := string(bb.Bytes())
  bb.Reset()
  t.byteBufferPool.Put(bb)
  return s, nil
}

func (t *Template) ExecuteString(m map[string]interface{}) string {
  return t.ExecuteFuncString(func(w io.Writer, tag string) (int, error) { return stdTagFunc(w, tag, m) })
}

func (t *Template) ExecuteStringStd(m map[string]interface{}) string {
  return t.ExecuteFuncString(func(w io.Writer, tag string) (int, error) { return keepUnknownTagFunc(w, t.startTag, t.endTag, tag, m) })
}

Execute() 方法构造一个 TagFunc 并调用 ExecuteFunc(),内部使用 stdTagFunc:

func(w io.Writer, tag string) (int, error) {
  return stdTagFunc(w, tag, m)
}

ExecuteStd()方法构造一个TagFunc来调用ExecuteFunc(),内部使用keepUnknownTagFunc:

func(w io.Writer, tag string) (int, error) {
  return keepUnknownTagFunc(w, t.startTag, t.endTag, tag, m)
}

executestring() 和 executestringstd() 方法调用 executefuncstring() 方法,executefuncstring() 方法调用 executefuncstringwitherr() 方法。 ExecuteFuncStringWithErr()方法内部使用bytebufferpool.Get()获取bytebufferpoo.Buffer对象来调用ExecuteFunc()方法。 所以核心就是ExecuteFunc()方法:

func (t *Template) ExecuteFunc(w io.Writer, f TagFunc) (int64, error) {
  var nn int64

  n := len(t.texts) - 1
  if n == -1 {
    ni, err := w.Write(unsafeString2Bytes(t.template))
    return int64(ni), err
  }

  for i := 0; i < n; i++ {
    ni, err := w.Write(t.texts[i])
    nn += int64(ni)
    if err != nil {
      return nn, err
    }

    ni, err = f(w, t.tags[i])
    nn += int64(ni)
    if err != nil {
      return nn, err
    }
  }
  ni, err := w.Write(t.texts[n])
  nn += int64(ni)
  return nn, err
}

整个逻辑也非常清晰。 for 循环写入文本元素,使用当前标签执行 TagFunc,并索引 +1。 最后写下最后一个文本元素并完成。 大概是这样的:

| text | tag | text | tag | text | ... | tag | text |

注意:ExecuteFuncStringWithErr()方法使用了上一篇文章中介绍的bytebufferpool。 如果您有兴趣,可以回来阅读。

总结

fasttemplate可以用来完成strings.Replace和fmt.Sprintf的任务,而且fasttemplate更加灵活。 代码清晰易懂,值得一看。

吐槽:关于命名,在Execute()方法上使用stdTagFunc,在ExecuteStd()方法上使用keepUnknownTagFunc技巧。 我认为将 stdTagFunc 重命名为 defaultTagFunc 会更好吗?

如果您发现有趣且好用的 Go 语言库,欢迎在 Go Daily Library GitHub 上提交 Issue

参考

快速模板 GitHub:github.com/valyala/fasttemplate

Go 每日图书馆 GitHub:

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

悟空资源网 html html空格占位符-去每日图书馆快速模板 https://www.wkzy.net/game/192331.html

常见问题

相关文章

官方客服团队

为您解决烦忧 - 24小时在线 专业服务