gf/g/os/gview/gview.go

439 lines
14 KiB
Go
Raw Normal View History

// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
2017-12-29 16:03:30 +08:00
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
2017-12-29 16:03:30 +08:00
2019-01-15 23:27:47 +08:00
// Package gview implements a template engine based on text/template.
package gview
import (
"bytes"
"errors"
"fmt"
"github.com/gogf/gf"
"github.com/gogf/gf/g/container/garray"
"github.com/gogf/gf/g/encoding/ghash"
"github.com/gogf/gf/g/encoding/ghtml"
"github.com/gogf/gf/g/encoding/gurl"
"github.com/gogf/gf/g/internal/cmdenv"
"github.com/gogf/gf/g/os/gfcache"
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/os/glog"
"github.com/gogf/gf/g/os/gspath"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/os/gview/internal/text/template"
"github.com/gogf/gf/g/text/gstr"
"github.com/gogf/gf/g/util/gconv"
"strings"
"sync"
)
// 视图对象
type View struct {
mu sync.RWMutex
paths *garray.StringArray // 模板查找目录(绝对路径)
data map[string]interface{} // 模板变量
funcmap map[string]interface{} // FuncMap
delimiters []string // 模板变量分隔符号
}
2018-08-23 21:55:27 +08:00
// 模板变量
type Params = map[string]interface{}
// 函数映射表
type FuncMap = map[string]interface{}
// 默认的视图对象
var defaultViewObj *View
2018-09-26 09:58:49 +08:00
// 初始化默认的视图对象, 默认加载包不会初始化,使用包方法才会初始化模板引擎对象。
2018-09-26 09:58:49 +08:00
func checkAndInitDefaultView() {
if defaultViewObj == nil {
defaultViewObj = New(gfile.Pwd())
2018-09-26 09:58:49 +08:00
}
}
// 直接解析模板内容,返回解析后的内容
func ParseContent(content string, params Params) ([]byte, error) {
2018-09-26 09:58:49 +08:00
checkAndInitDefaultView()
return defaultViewObj.ParseContent(content, params)
}
// 生成一个视图对象
func New(path...string) *View {
view := &View {
paths : garray.NewStringArray(),
data : make(map[string]interface{}),
funcmap : make(map[string]interface{}),
delimiters : make([]string, 2),
}
if len(path) > 0 && len(path[0]) > 0 {
view.SetPath(path[0])
} else {
// Customized dir path from env/cmd.
if envPath := cmdenv.Get("gf.gview.path").String(); envPath != "" {
if gfile.Exists(envPath) {
view.SetPath(envPath)
} else {
glog.Errorfln("Template directory path does not exist: %s", envPath)
}
} else {
// Dir path of working dir.
view.SetPath(gfile.Pwd())
// Dir path of binary.
if selfPath := gfile.SelfDir(); selfPath != "" && gfile.Exists(selfPath) {
view.AddPath(selfPath)
}
// Dir path of main package.
if mainPath := gfile.MainPkgPath(); mainPath != "" && gfile.Exists(mainPath) {
view.AddPath(mainPath)
}
}
}
view.SetDelimiters("{{", "}}")
// 内置变量
view.data["GF"] = map[string]interface{} {
"version" : gf.VERSION,
}
2018-08-23 21:55:27 +08:00
// 内置方法
view.BindFunc("text", view.funcText)
view.BindFunc("html", view.funcHtmlEncode)
view.BindFunc("htmlencode", view.funcHtmlEncode)
view.BindFunc("htmldecode", view.funcHtmlDecode)
view.BindFunc("url", view.funcUrlEncode)
view.BindFunc("urlencode", view.funcUrlEncode)
view.BindFunc("urldecode", view.funcUrlDecode)
view.BindFunc("date", view.funcDate)
view.BindFunc("substr", view.funcSubStr)
view.BindFunc("strlimit", view.funcStrLimit)
view.BindFunc("compare", view.funcCompare)
view.BindFunc("hidestr", view.funcHideStr)
view.BindFunc("highlight", view.funcHighlight)
view.BindFunc("toupper", view.funcToUpper)
view.BindFunc("tolower", view.funcToLower)
view.BindFunc("nl2br", view.funcNl2Br)
view.BindFunc("include", view.funcInclude)
return view
}
// 设置模板目录绝对路径
func (view *View) SetPath(path string) error {
// 判断绝对路径(或者工作目录下目录)
realPath := gfile.RealPath(path)
2019-02-14 13:38:52 +08:00
if realPath == "" {
// 判断相对路径
view.paths.RLockFunc(func(array []string) {
for _, v := range array {
if path, _ := gspath.Search(v, path); path != "" {
realPath = path
break
}
}
})
2019-02-14 13:38:52 +08:00
}
// 目录不存在错误处理
if realPath == "" {
buffer := bytes.NewBuffer(nil)
if view.paths.Len() > 0 {
buffer.WriteString(fmt.Sprintf("[gview] SetPath failed: cannot find directory \"%s\" in following paths:", path))
view.paths.RLockFunc(func(array []string) {
for k, v := range array {
buffer.WriteString(fmt.Sprintf("\n%d. %s",k + 1, v))
}
})
} else {
buffer.WriteString(fmt.Sprintf(`[gview] SetPath failed: path "%s" does not exist`, path))
}
err := errors.New(buffer.String())
glog.Error(err)
return err
}
// 路径必须为目录类型
if !gfile.IsDir(realPath) {
err := errors.New(fmt.Sprintf(`[gview] SetPath failed: path "%s" should be directory type`, path))
glog.Error(err)
2018-10-30 23:58:10 +08:00
return err
}
// 重复判断
if view.paths.Search(realPath) != -1 {
return nil
}
view.paths.Clear()
view.paths.Append(realPath)
//glog.Debug("[gview] SetPath:", realPath)
2018-10-30 23:58:10 +08:00
return nil
}
// 添加模板目录搜索路径
func (view *View) AddPath(path string) error {
// 判断绝对路径(或者工作目录下目录)
realPath := gfile.RealPath(path)
2019-02-14 13:38:52 +08:00
if realPath == "" {
// 判断相对路径
view.paths.RLockFunc(func(array []string) {
for _, v := range array {
if path, _ := gspath.Search(v, path); path != "" {
realPath = path
break
}
}
})
2019-02-14 13:38:52 +08:00
}
// 目录不存在错误处理
if realPath == "" {
buffer := bytes.NewBuffer(nil)
if view.paths.Len() > 0 {
buffer.WriteString(fmt.Sprintf("[gview] AddPath failed: cannot find directory \"%s\" in following paths:", path))
view.paths.RLockFunc(func(array []string) {
for k, v := range array {
buffer.WriteString(fmt.Sprintf("\n%d. %s",k + 1, v))
}
})
} else {
buffer.WriteString(fmt.Sprintf(`[gview] AddPath failed: path "%s" does not exist`, path))
}
err := errors.New(buffer.String())
glog.Error(err)
return err
}
// 路径必须为目录类型
if !gfile.IsDir(realPath) {
err := errors.New(fmt.Sprintf(`[gview] AddPath failed: path "%s" should be directory type`, path))
glog.Error(err)
2018-10-30 23:58:10 +08:00
return err
}
// 重复判断
if view.paths.Search(realPath) != -1 {
return nil
}
view.paths.Append(realPath)
//glog.Debug("[gview] AddPath:", realPath)
2018-10-30 23:58:10 +08:00
return nil
}
// 批量绑定模板变量,即调用之后每个线程都会生效,因此有并发安全控制
func (view *View) Assigns(data Params) {
view.mu.Lock()
for k, v := range data {
view.data[k] = v
}
view.mu.Unlock()
}
// 绑定模板变量,即调用之后每个线程都会生效,因此有并发安全控制
func (view *View) Assign(key string, value interface{}) {
view.mu.Lock()
view.data[key] = value
view.mu.Unlock()
}
2018-04-17 13:54:33 +08:00
// 解析模板,返回解析后的内容
func (view *View) Parse(file string, params Params, funcmap...map[string]interface{}) ([]byte, error) {
path := ""
view.paths.RLockFunc(func(array []string) {
for _, v := range array {
if path, _ = gspath.Search(v, file); path != "" {
break
}
}
})
2018-08-21 21:18:56 +08:00
if path == "" {
buffer := bytes.NewBuffer(nil)
if view.paths.Len() > 0 {
buffer.WriteString(fmt.Sprintf("[gview] cannot find template file \"%s\" in following paths:", file))
view.paths.RLockFunc(func(array []string) {
for k, v := range array {
buffer.WriteString(fmt.Sprintf("\n%d. %s",k + 1, v))
}
})
} else {
buffer.WriteString(fmt.Sprintf("[gview] cannot find template file \"%s\" with no path set/add", file))
}
glog.Error(buffer.String())
return nil, errors.New(fmt.Sprintf(`tpl "%s" not found`, file))
}
2018-08-21 21:18:56 +08:00
content := gfcache.GetContents(path)
// 执行模板解析互斥锁主要是用于funcmap
view.mu.RLock()
defer view.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
2018-08-23 21:55:27 +08:00
tplobj := template.New(path).Delims(view.delimiters[0], view.delimiters[1]).Funcs(view.funcmap)
if len(funcmap) > 0 {
tplobj = tplobj.Funcs(funcmap[0])
}
if tpl, err := tplobj.Parse(content); err != nil {
2017-12-28 15:21:25 +08:00
return nil, err
2018-04-17 13:54:33 +08:00
} else {
// 注意模板变量赋值不能改变已有的params或者view.data的值因为这两个变量都是指针
// 因此在必要条件下需要合并两个map的值到一个新的map
vars := (map[string]interface{})(nil)
if len(view.data) > 0 {
if len(params) > 0 {
vars = make(map[string]interface{}, len(view.data) + len(params))
for k, v := range params {
vars[k] = v
}
for k, v := range view.data {
vars[k] = v
}
} else {
vars = view.data
}
} else {
vars = params
}
if err := tpl.Execute(buffer, vars); err != nil {
2018-04-17 13:54:33 +08:00
return nil, err
}
}
return buffer.Bytes(), nil
}
// 直接解析模板内容,返回解析后的内容
func (view *View) ParseContent(content string, params Params, funcmap...map[string]interface{}) ([]byte, error) {
view.mu.RLock()
defer view.mu.RUnlock()
name := gconv.String(ghash.BKDRHash64([]byte(content)))
2018-04-17 13:54:33 +08:00
buffer := bytes.NewBuffer(nil)
2018-08-23 21:55:27 +08:00
tplobj := template.New(name).Delims(view.delimiters[0], view.delimiters[1]).Funcs(view.funcmap)
if len(funcmap) > 0 {
tplobj = tplobj.Funcs(funcmap[0])
}
if tpl, err := tplobj.Parse(content); err != nil {
2018-04-17 13:54:33 +08:00
return nil, err
} else {
// 注意模板变量赋值不能改变已有的params或者view.data的值因为这两个变量都是指针
// 因此在必要条件下需要合并两个map的值到一个新的map
vars := (map[string]interface{})(nil)
if len(view.data) > 0 {
if len(params) > 0 {
vars = make(map[string]interface{}, len(view.data) + len(params))
for k, v := range params {
vars[k] = v
}
for k, v := range view.data {
vars[k] = v
}
} else {
vars = view.data
}
} else {
vars = params
}
if err := tpl.Execute(buffer, vars); err != nil {
2017-12-28 15:21:25 +08:00
return nil, err
}
}
2017-12-28 15:21:25 +08:00
return buffer.Bytes(), nil
}
// 设置模板变量解析分隔符号
func (view *View) SetDelimiters(left, right string) {
view.delimiters[0] = left
view.delimiters[1] = right
}
// 绑定自定义函数,该函数是全局有效,即调用之后每个线程都会生效,因此有并发安全控制
func (view *View) BindFunc(name string, function interface{}) {
view.mu.Lock()
view.funcmap[name] = function
view.mu.Unlock()
}
2017-12-30 18:35:24 +08:00
// 模板内置方法include
func (view *View) funcInclude(file string, data...map[string]interface{}) string {
var m map[string]interface{} = nil
if len(data) > 0 {
m = data[0]
}
content, err := view.Parse(file, m)
2017-12-30 18:35:24 +08:00
if err != nil {
return err.Error()
2017-12-30 18:35:24 +08:00
}
return string(content)
2017-12-30 18:35:24 +08:00
}
2018-09-03 13:05:41 +08:00
// 模板内置方法text
func (view *View) funcText(html interface{}) string {
return ghtml.StripTags(gconv.String(html))
}
// 模板内置方法html
func (view *View) funcHtmlEncode(html interface{}) string {
return ghtml.Entities(gconv.String(html))
}
// 模板内置方法htmldecode
func (view *View) funcHtmlDecode(html interface{}) string {
return ghtml.EntitiesDecode(gconv.String(html))
}
// 模板内置方法url
func (view *View) funcUrlEncode(url interface{}) string {
return gurl.Encode(gconv.String(url))
}
// 模板内置方法urldecode
func (view *View) funcUrlDecode(url interface{}) string {
if content, err := gurl.Decode(gconv.String(url)); err == nil {
return content
} else {
return err.Error()
}
}
// 模板内置方法date
func (view *View) funcDate(format string, timestamp...interface{}) string {
t := int64(0)
if len(timestamp) > 0 {
t = gconv.Int64(timestamp[0])
}
if t == 0 {
t = gtime.Millisecond()
}
return gtime.NewFromTimeStamp(t).Format(format)
}
// 模板内置方法compare
func (view *View) funcCompare(value1, value2 interface{}) int {
return strings.Compare(gconv.String(value1), gconv.String(value2))
}
// 模板内置方法substr
func (view *View) funcSubStr(start, end int, str interface{}) string {
return gstr.SubStr(gconv.String(str), start, end)
}
// 模板内置方法strlimit
func (view *View) funcStrLimit(length int, suffix string, str interface{}) string {
return gstr.StrLimit(gconv.String(str), length, suffix)
}
// 模板内置方法highlight
func (view *View) funcHighlight(key string, color string, str interface{}) string {
return gstr.Replace(gconv.String(str), key, fmt.Sprintf(`<span style="color:%s;">%s</span>`, color, key))
}
// 模板内置方法hidestr
func (view *View) funcHideStr(percent int, hide string, str interface{}) string {
return gstr.HideStr(gconv.String(str), percent, hide)
}
// 模板内置方法toupper
func (view *View) funcToUpper(str interface{}) string {
return gstr.ToUpper(gconv.String(str))
}
// 模板内置方法toupper
func (view *View) funcToLower(str interface{}) string {
return gstr.ToLower(gconv.String(str))
}
// 模板内置方法nl2br
func (view *View) funcNl2Br(str interface{}) string {
return gstr.Nl2Br(gconv.String(str))
}