简化模板引擎功能,完善模板引擎示例

This commit is contained in:
John
2018-01-08 17:07:05 +08:00
parent 209de2c3ed
commit 7fecb2a9f0
6 changed files with 89 additions and 124 deletions

View File

@ -8,10 +8,9 @@ package gmvc
import (
"sync"
"html/template"
"gitee.com/johng/gf/g/os/gview"
"gitee.com/johng/gf/g/frame/gins"
"gitee.com/johng/gf/g/net/ghttp"
"gitee.com/johng/gf/g/frame/gins"
)
// MVC视图基类(一个请求一个视图对象,用完即销毁)
@ -49,23 +48,15 @@ func (view *View) Assign(key string, value interface{}) {
// 解析模板,并返回解析后的内容
func (view *View) Parse(file string) ([]byte, error) {
// 查询模板
tpl, err := view.view.Template(file)
if err != nil {
return nil, err
}
// 绑定函数对基类的include进行覆盖(由于涉及到模板变量的传递)
tpl.BindFunc("include", view.funcInclude)
// 执行解析
view.mu.RLock()
content, err := tpl.Parse(view.data)
content, err := view.view.Parse(file, view.data)
view.mu.RUnlock()
return content, err
}
// 解析指定模板
func (view *View) Display(files...string) error {
file := "default"
file := "index.tpl"
if len(files) > 0 {
file = files[0]
}
@ -76,17 +67,4 @@ func (view *View) Display(files...string) error {
view.response.Write(content)
}
return nil
}
// 模板内置方法include
func (view *View) funcInclude(file string) template.HTML {
tpl, err := view.view.Template(file)
if err != nil {
return template.HTML(err.Error())
}
content, err := tpl.Parse(view.data)
if err != nil {
return template.HTML(err.Error())
}
return template.HTML(content)
}
}

View File

@ -8,30 +8,21 @@
package gview
import (
"gitee.com/johng/gf/g/container/gmap"
"html/template"
"gitee.com/johng/gf/g/os/gfile"
"sync"
"strings"
"bytes"
"errors"
"strings"
"html/template"
"gitee.com/johng/gf/g/os/gfile"
"gitee.com/johng/gf/g/container/gmap"
)
// 视图对象
type View struct {
mu sync.RWMutex
path string // 模板目录(绝对路径)
tpls *gmap.StringInterfaceMap // 已解析的模板对象指针,防止重复解析
suffix string // 模板文件名后缀
}
// 模板对象
type Template struct {
mu sync.RWMutex // 并发互斥锁
path string // 模板文件(绝对路径)
data map[string]interface{} // 全局的模板变量
content string // 模板文件内容(解析之后保存到内存中)
funcmap map[string]interface{} // FuncMap
mu sync.RWMutex
path string // 模板目录(绝对路径)
funcmap map[string]interface{} // FuncMap
contents *gmap.StringStringMap // 已解析的模板文件内容
}
// 视图表
@ -49,11 +40,13 @@ func Get(path string) *View {
// 生成一个视图对象
func New(path string) *View {
return &View {
path : path,
tpls : gmap.NewStringInterfaceMap(),
suffix : "tpl",
view := &View {
path : path,
funcmap : make(map[string]interface{}),
contents : gmap.NewStringStringMap(),
}
view.BindFunc("include", view.funcInclude)
return view
}
// 设置模板目录绝对路径
@ -70,89 +63,67 @@ func (view *View) GetPath() string {
return view.path
}
// 获取模板文件内容
func (view *View) GetTplContent() string {
view.mu.RLock()
defer view.mu.RUnlock()
return view.path
}
// 直接解析模板,返回解析后的内容
func (view *View) Parse(file string, params map[string]interface{}) ([]byte, error) {
if t, err := view.Template(file); err == nil {
return t.Parse(params)
} else {
return nil, err
}
}
// 根据文件名称生成一个模板对象,或者获取一个现有的模板对象
func (view *View) Template(file string) (*Template, error) {
path := strings.TrimRight(view.GetPath(), gfile.Separator) + gfile.Separator + file + "." + view.suffix
if t := view.tpls.Get(path); t != nil {
return t.(*Template), nil
}
if !gfile.Exists(path) {
return nil, errors.New("template '" + path + "' does not exist")
}
if !gfile.IsReadable(path) {
return nil, errors.New("template '" + path + "' is not readable")
}
t := &Template{
path : path,
data : make(map[string]interface{}),
content : gfile.GetContents(path),
funcmap : make(map[string]interface{}),
}
// 绑定内置inluce方法
t.BindFunc("include", t.funcInclude)
// 将模板对象注册到内部map中
view.tpls.Set(path, t)
return t, nil
}
// 绑定自定义函数,该函数是全局有效,即调用之后每个线程都会生效,因此有并发安全控制
func (t *Template) BindFunc(name string, function interface{}) {
t.mu.Lock()
defer t.mu.Unlock()
t.funcmap[name] = function
}
// 批量绑定模板变量,即调用之后每个线程都会生效,因此有并发安全控制
func (t *Template) Assigns(data map[string]interface{}) {
t.mu.Lock()
defer t.mu.Unlock()
for k, v := range data {
t.data[k] = v
}
}
// 绑定模板变量,即调用之后每个线程都会生效,因此有并发安全控制
func (t *Template) Assign(k string, v interface{}) {
t.mu.Lock()
defer t.mu.Unlock()
t.data[k] = v
}
// 返回解析后的模板内容可以额外指定模板变量如果没有可以传入nil
// 函数内部的底层template必须每次调用都新生成一个防止错误html/template: cannot Parse after Execute
func (t *Template) Parse(data map[string]interface{}) ([]byte, error) {
t.mu.RLock()
defer t.mu.RUnlock()
buffer := bytes.NewBuffer(nil)
if tpl, err := template.New(t.path).Funcs(t.funcmap).Parse(t.content); err != nil {
return nil, err
} else {
m := t.data
for k, v := range data {
m[k] = v
// 获取模板文件路径及内容
path := strings.TrimRight(view.GetPath(), gfile.Separator) + gfile.Separator + file
content := view.contents.Get(path)
if content == "" {
content = gfile.GetContents(path)
if content == "" {
content = gfile.GetContents(file)
}
if err := tpl.Execute(buffer, m); err != nil {
view.contents.Set(path, content)
}
if content == "" {
return nil, errors.New("invalid tpl \"" + file + "\"")
}
// 执行模板解析
buffer := bytes.NewBuffer(nil)
if tpl, err := template.New(path).Funcs(view.getFuncs()).Parse(content); err != nil {
return nil, err
} else {
if err := tpl.Execute(buffer, params); err != nil {
return nil, err
}
}
return buffer.Bytes(), nil
}
// 绑定自定义函数,该函数是全局有效,即调用之后每个线程都会生效,因此有并发安全控制
func (view *View) BindFunc(name string, function interface{}) {
view.mu.Lock()
defer view.mu.Unlock()
view.funcmap[name] = function
}
// 获取模板自定义函数,每一次都是一份拷贝
func (view *View) getFuncs() map[string]interface{} {
m := make(map[string]interface{})
view.mu.RLock()
for k, v := range view.funcmap {
m[k] = v
}
view.mu.RUnlock()
return m
}
// 模板内置方法include
func (t *Template) funcInclude(file string) template.HTML {
content, err := t.Parse(nil)
func (view *View) funcInclude(file string, datas...map[string]interface{}) template.HTML {
var data map[string]interface{} = nil
if len(datas) > 0 {
data = datas[0]
}
content, err := view.Parse(file, data)
if err != nil {
return template.HTML(err.Error())
}
return template.HTML(content)
}

View File

@ -3,6 +3,7 @@ package demo
import (
"gitee.com/johng/gf/g/net/ghttp"
"gitee.com/johng/gf/g/frame/gmvc"
"gitee.com/johng/gf/g/frame/gins"
)
type ControllerTemplate struct {
@ -10,7 +11,7 @@ type ControllerTemplate struct {
}
func init() {
ghttp.GetServer().BindControllerMethod("/template/info", &ControllerTemplate{}, "Info")
ghttp.GetServer().BindControllerMethod("/template", &ControllerTemplate{}, "Info")
}
func (c *ControllerTemplate) Info() {
@ -19,7 +20,7 @@ func (c *ControllerTemplate) Info() {
"age" : 18,
"score" : 100,
})
c.View.Display("user/index")
c.View.Display("user/index.tpl")
}

View File

@ -7,9 +7,7 @@ import (
func init() {
ghttp.GetServer().BindHandler("/template2", func(r *ghttp.Request){
view := gins.View()
view.SetPath("/home/www/template/")
content, _ := view.Parse("index", map[string]interface{}{
content, _ := gins.View().Parse("index.tpl", map[string]interface{}{
"id" : 123,
"name" : "john",
})

View File

@ -0,0 +1,17 @@
package demo
import (
"gitee.com/johng/gf/g/net/ghttp"
"gitee.com/johng/gf/g/frame/gins"
)
func init() {
gins.View().SetPath("/home/www/template/")
ghttp.GetServer().BindHandler("/template3", func(r *ghttp.Request){
content, _ := gins.View().Parse("index.tpl", map[string]interface{}{
"id" : 123,
"name" : "john",
})
r.Response.Write(content)
})
}

View File

@ -6,6 +6,6 @@
<body>
<h3>This is index</h3>
<p>tpl vals: {{.}}</p>
{{include "user/footer"}}
{{include "user/footer.tpl" }}
</body>
</html>