Files
gf/g/os/gview/gview.go

232 lines
6.8 KiB
Go
Raw Normal View History

2017-12-29 16:03:30 +08:00
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// 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://gitee.com/johng/gf.
// 视图管理.
package gview
import (
"sync"
"bytes"
"errors"
"html/template"
"gitee.com/johng/gf/g/container/gmap"
"gitee.com/johng/gf/g/encoding/ghash"
"gitee.com/johng/gf/g/util/gconv"
"gitee.com/johng/gf/g/os/gspath"
2018-08-21 21:18:56 +08:00
"gitee.com/johng/gf/g/os/gfcache"
"gitee.com/johng/gf/g/encoding/ghtml"
)
// 视图对象
type View struct {
mu sync.RWMutex
paths *gspath.SPath // 模板查找目录(绝对路径)
data map[string]interface{} // 模板变量
funcmap map[string]interface{} // FuncMap
delimiters []string // 模板变量分隔符号
}
2018-08-23 21:55:27 +08:00
// 输出到模板页面时保留HTML标签原意不做自动escape处理
type HTML = template.HTML
// 模板变量
type Params = map[string]interface{}
// 函数映射表
type FuncMap = map[string]interface{}
// 视图表
var viewMap = gmap.NewStringInterfaceMap()
// 默认的视图对象
2018-09-26 09:58:49 +08:00
var viewObj *View
// 初始化默认的视图对象
func checkAndInitDefaultView() {
if viewObj == nil {
viewObj = Get(".")
}
}
// 直接解析模板内容,返回解析后的内容
func ParseContent(content string, params Params) ([]byte, error) {
2018-09-26 09:58:49 +08:00
checkAndInitDefaultView()
return viewObj.ParseContent(content, params)
}
// 获取或者创建一个视图对象
2018-01-03 10:38:53 +08:00
func Get(path string) *View {
if r := viewMap.Get(path); r != nil {
return r.(*View)
}
v := New(path)
viewMap.Set(path, v)
return v
}
// 生成一个视图对象
func New(path string) *View {
s := gspath.New()
s.Set(path)
view := &View {
paths : s,
data : make(map[string]interface{}),
funcmap : make(map[string]interface{}),
delimiters : make([]string, 2),
}
view.SetDelimiters("{{", "}}")
2018-08-23 21:55:27 +08:00
// 内置方法
view.BindFunc("text", view.funcText)
view.BindFunc("html", view.funcHtml)
view.BindFunc("include", view.funcInclude)
return view
}
// 设置模板目录绝对路径
func (view *View) SetPath(path string) error {
return view.paths.Set(path)
}
// 添加模板目录搜索路径
func (view *View) AddPath(path string) error {
return view.paths.Add(path)
}
// 批量绑定模板变量,即调用之后每个线程都会生效,因此有并发安全控制
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) {
2018-08-21 21:18:56 +08:00
path := view.paths.Search(file)
if path == "" {
return nil, errors.New("tpl \"" + file + "\" not found")
}
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{}) template.HTML {
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 template.HTML(err.Error())
}
return template.HTML(content)
}
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) funcHtml(html interface{}) template.HTML {
return template.HTML(gconv.String(html))
}