1
0
mirror of https://github.com/astaxie/beego.git synced 2024-11-01 03:30:54 +00:00
Beego/template.go

407 lines
12 KiB
Go
Raw Normal View History

2014-08-18 08:41:43 +00:00
// Copyright 2014 beego Author. All Rights Reserved.
2014-07-03 15:40:21 +00:00
//
2014-08-18 08:41:43 +00:00
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
2014-07-03 15:40:21 +00:00
//
2014-08-18 08:41:43 +00:00
// http://www.apache.org/licenses/LICENSE-2.0
2014-07-03 15:40:21 +00:00
//
2014-08-18 08:41:43 +00:00
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
2013-03-14 09:57:09 +00:00
2014-08-18 08:41:43 +00:00
package beego
2013-03-14 09:57:09 +00:00
import (
"errors"
"fmt"
"html/template"
2016-03-04 06:49:16 +00:00
"io"
2013-09-12 09:20:32 +00:00
"io/ioutil"
"net/http"
2013-03-14 09:57:09 +00:00
"os"
"path/filepath"
2013-09-12 09:20:32 +00:00
"regexp"
2013-03-14 09:57:09 +00:00
"strings"
"sync"
"github.com/astaxie/beego/logs"
"github.com/astaxie/beego/utils"
2013-03-14 09:57:09 +00:00
)
var (
2017-03-17 17:24:45 +00:00
beegoTplFuncMap = make(template.FuncMap)
beeViewPathTemplateLocked = false
// beeViewPathTemplates caching map and supported template file extensions per view
2017-03-17 17:24:45 +00:00
beeViewPathTemplates = make(map[string]map[string]*template.Template)
templatesLock sync.RWMutex
2016-03-04 04:00:43 +00:00
// beeTemplateExt stores the template extension which will build
beeTemplateExt = []string{"tpl", "html", "gohtml"}
// beeTemplatePreprocessors stores associations of extension -> preprocessor handler
2016-05-24 03:43:18 +00:00
beeTemplateEngines = map[string]templatePreProcessor{}
beeTemplateFS = defaultFSFunc
2016-05-24 03:43:18 +00:00
)
2013-03-14 09:57:09 +00:00
2016-04-01 10:10:00 +00:00
// ExecuteTemplate applies the template with name to the specified data object,
// writing the output to wr.
// A template will be executed safely in parallel.
func ExecuteTemplate(wr io.Writer, name string, data interface{}) error {
2017-03-17 17:24:45 +00:00
return ExecuteViewPathTemplate(wr, name, BConfig.WebConfig.ViewsPath, data)
2017-02-07 16:28:03 +00:00
}
// ExecuteViewPathTemplate applies the template with name and from specific viewPath to the specified data object,
// writing the output to wr.
// A template will be executed safely in parallel.
2017-02-07 16:28:03 +00:00
func ExecuteViewPathTemplate(wr io.Writer, name string, viewPath string, data interface{}) error {
2016-03-04 06:49:16 +00:00
if BConfig.RunMode == DEV {
2016-03-04 04:00:43 +00:00
templatesLock.RLock()
defer templatesLock.RUnlock()
}
2017-03-17 17:24:45 +00:00
if beeTemplates, ok := beeViewPathTemplates[viewPath]; ok {
2017-02-07 16:28:03 +00:00
if t, ok := beeTemplates[name]; ok {
var err error
if t.Lookup(name) != nil {
err = t.ExecuteTemplate(wr, name, data)
} else {
err = t.Execute(wr, data)
}
if err != nil {
logs.Trace("template Execute err:", err)
}
return err
2016-07-28 03:56:55 +00:00
}
2017-02-07 16:28:03 +00:00
panic("can't find templatefile in the path:" + viewPath + "/" + name)
2016-03-04 06:49:16 +00:00
}
panic("Unknown view path:" + viewPath)
2016-03-04 04:00:43 +00:00
}
2013-03-14 09:57:09 +00:00
func init() {
beegoTplFuncMap["dateformat"] = DateFormat
beegoTplFuncMap["date"] = Date
beegoTplFuncMap["compare"] = Compare
beegoTplFuncMap["compare_not"] = CompareNot
beegoTplFuncMap["not_nil"] = NotNil
beegoTplFuncMap["not_null"] = NotNil
2013-03-14 13:47:39 +00:00
beegoTplFuncMap["substr"] = Substr
2015-09-08 15:41:41 +00:00
beegoTplFuncMap["html2str"] = HTML2str
beegoTplFuncMap["str2html"] = Str2html
beegoTplFuncMap["htmlquote"] = Htmlquote
beegoTplFuncMap["htmlunquote"] = Htmlunquote
2013-08-10 08:33:46 +00:00
beegoTplFuncMap["renderform"] = RenderForm
beegoTplFuncMap["assets_js"] = AssetsJs
2015-09-08 15:41:41 +00:00
beegoTplFuncMap["assets_css"] = AssetsCSS
2016-01-17 16:18:21 +00:00
beegoTplFuncMap["config"] = GetConfig
2015-08-16 19:08:02 +00:00
beegoTplFuncMap["map_get"] = MapGet
// Comparisons
beegoTplFuncMap["eq"] = eq // ==
beegoTplFuncMap["ge"] = ge // >=
beegoTplFuncMap["gt"] = gt // >
beegoTplFuncMap["le"] = le // <=
beegoTplFuncMap["lt"] = lt // <
beegoTplFuncMap["ne"] = ne // !=
beegoTplFuncMap["urlfor"] = URLFor // build a URL to match a Controller and it's method
2013-03-14 09:57:09 +00:00
}
// AddFuncMap let user to register a func in the template.
func AddFuncMap(key string, fn interface{}) error {
beegoTplFuncMap[key] = fn
2013-03-14 09:57:09 +00:00
return nil
}
type templatePreProcessor func(root, path string, funcs template.FuncMap) (*template.Template, error)
type templateFile struct {
2013-03-14 09:57:09 +00:00
root string
files map[string][]string
}
// visit will make the paths into two part,the first is subDir (without tf.root),the second is full path(without tf.root).
// if tf.root="views" and
// paths is "views/errors/404.html",the subDir will be "errors",the file will be "errors/404.html"
// paths is "views/admin/errors/404.html",the subDir will be "admin/errors",the file will be "admin/errors/404.html"
func (tf *templateFile) visit(paths string, f os.FileInfo, err error) error {
2013-03-14 09:57:09 +00:00
if f == nil {
return err
}
2013-08-01 03:57:29 +00:00
if f.IsDir() || (f.Mode()&os.ModeSymlink) > 0 {
2013-03-14 09:57:09 +00:00
return nil
2013-08-01 03:57:29 +00:00
}
if !HasTemplateExt(paths) {
2013-03-14 09:57:09 +00:00
return nil
2013-08-01 03:57:29 +00:00
}
replace := strings.NewReplacer("\\", "/")
file := strings.TrimLeft(replace.Replace(paths[len(tf.root):]), "/")
subDir := filepath.Dir(file)
2013-03-14 09:57:09 +00:00
tf.files[subDir] = append(tf.files[subDir], file)
2013-08-01 03:57:29 +00:00
return nil
}
2015-09-08 15:29:58 +00:00
// HasTemplateExt return this path contains supported template extension of beego or not.
func HasTemplateExt(paths string) bool {
2016-03-04 04:00:43 +00:00
for _, v := range beeTemplateExt {
2013-08-01 03:57:29 +00:00
if strings.HasSuffix(paths, "."+v) {
return true
2013-03-14 09:57:09 +00:00
}
}
2013-08-01 03:57:29 +00:00
return false
2013-03-14 09:57:09 +00:00
}
2015-09-08 15:29:58 +00:00
// AddTemplateExt add new extension for template.
2013-03-14 09:57:09 +00:00
func AddTemplateExt(ext string) {
2016-03-04 04:00:43 +00:00
for _, v := range beeTemplateExt {
2013-03-14 09:57:09 +00:00
if v == ext {
return
}
}
2016-03-04 04:00:43 +00:00
beeTemplateExt = append(beeTemplateExt, ext)
2013-03-14 09:57:09 +00:00
}
2017-03-17 17:24:45 +00:00
// AddViewPath adds a new path to the supported view paths.
//Can later be used by setting a controller ViewPath to this folder
2017-03-17 17:24:45 +00:00
//will panic if called after beego.Run()
2017-02-07 16:28:03 +00:00
func AddViewPath(viewPath string) error {
if beeViewPathTemplateLocked {
if _, exist := beeViewPathTemplates[viewPath]; exist {
return nil //Ignore if viewpath already exists
}
panic("Can not add new view paths after beego.Run()")
}
2017-02-07 16:28:03 +00:00
beeViewPathTemplates[viewPath] = make(map[string]*template.Template)
return BuildTemplate(viewPath)
}
func lockViewPaths() {
beeViewPathTemplateLocked = true
}
2015-09-08 15:29:58 +00:00
// BuildTemplate will build all template files in a directory.
// it makes beego can render any template file in view directory.
func BuildTemplate(dir string, files ...string) error {
var err error
fs := beeTemplateFS()
f, err := fs.Open(dir)
if err != nil {
2013-03-21 13:45:48 +00:00
if os.IsNotExist(err) {
return nil
2013-03-21 13:45:48 +00:00
}
2015-09-08 15:29:58 +00:00
return errors.New("dir open err")
2013-03-21 12:46:54 +00:00
}
defer f.Close()
2017-03-17 17:24:45 +00:00
beeTemplates, ok := beeViewPathTemplates[dir]
2017-02-07 16:28:03 +00:00
if !ok {
panic("Unknown view path: " + dir)
}
self := &templateFile{
2013-03-14 09:57:09 +00:00
root: dir,
files: make(map[string][]string),
}
err = Walk(fs, dir, func(path string, f os.FileInfo, err error) error {
2013-09-12 09:20:32 +00:00
return self.visit(path, f, err)
2013-03-14 09:57:09 +00:00
})
if err != nil {
2018-11-06 15:40:43 +00:00
fmt.Printf("Walk() returned %v\n", err)
2013-03-14 09:57:09 +00:00
return err
}
buildAllFiles := len(files) == 0
2013-09-13 05:57:40 +00:00
for _, v := range self.files {
for _, file := range v {
if buildAllFiles || utils.InSlice(file, files) {
templatesLock.Lock()
ext := filepath.Ext(file)
var t *template.Template
if len(ext) == 0 {
t, err = getTemplate(self.root, fs, file, v...)
} else if fn, ok := beeTemplateEngines[ext[1:]]; ok {
t, err = fn(self.root, file, beegoTplFuncMap)
} else {
t, err = getTemplate(self.root, fs, file, v...)
}
if err != nil {
logs.Error("parse template err:", file, err)
2018-04-20 10:42:50 +00:00
templatesLock.Unlock()
2017-09-02 09:55:26 +00:00
return err
}
2017-11-25 17:16:37 +00:00
beeTemplates[file] = t
templatesLock.Unlock()
2013-09-13 05:57:40 +00:00
}
}
}
2013-03-14 09:57:09 +00:00
return nil
}
2013-09-12 09:20:32 +00:00
func getTplDeep(root string, fs http.FileSystem, file string, parent string, t *template.Template) (*template.Template, [][]string, error) {
var fileAbsPath string
2017-04-18 23:47:05 +00:00
var rParent string
var err error
2019-01-22 11:09:57 +00:00
if strings.HasPrefix(file, "../") {
2017-04-18 23:47:05 +00:00
rParent = filepath.Join(filepath.Dir(parent), file)
fileAbsPath = filepath.Join(root, filepath.Dir(parent), file)
2013-10-30 15:02:53 +00:00
} else {
2017-04-18 23:47:05 +00:00
rParent = file
fileAbsPath = filepath.Join(root, file)
2013-10-30 15:02:53 +00:00
}
f, err := fs.Open(fileAbsPath)
if err != nil {
2014-03-27 00:49:57 +00:00
panic("can't find template file:" + file)
2013-10-30 15:02:53 +00:00
}
2019-01-22 11:09:57 +00:00
defer f.Close()
data, err := ioutil.ReadAll(f)
2013-09-12 09:20:32 +00:00
if err != nil {
2013-09-13 09:41:31 +00:00
return nil, [][]string{}, err
2013-09-12 09:20:32 +00:00
}
t, err = t.New(file).Parse(string(data))
if err != nil {
2013-09-13 09:41:31 +00:00
return nil, [][]string{}, err
2013-09-12 09:20:32 +00:00
}
2015-12-09 15:35:04 +00:00
reg := regexp.MustCompile(BConfig.WebConfig.TemplateLeft + "[ ]*template[ ]+\"([^\"]+)\"")
allSub := reg.FindAllStringSubmatch(string(data), -1)
for _, m := range allSub {
2013-09-12 09:20:32 +00:00
if len(m) == 2 {
tl := t.Lookup(m[1])
if tl != nil {
2013-09-12 09:20:32 +00:00
continue
}
if !HasTemplateExt(m[1]) {
2013-09-12 09:20:32 +00:00
continue
}
_, _, err = getTplDeep(root, fs, m[1], rParent, t)
2013-10-30 15:02:53 +00:00
if err != nil {
return nil, [][]string{}, err
2013-09-12 09:20:32 +00:00
}
}
}
return t, allSub, nil
2013-09-12 09:20:32 +00:00
}
func getTemplate(root string, fs http.FileSystem, file string, others ...string) (t *template.Template, err error) {
2015-12-09 15:35:04 +00:00
t = template.New(file).Delims(BConfig.WebConfig.TemplateLeft, BConfig.WebConfig.TemplateRight).Funcs(beegoTplFuncMap)
var subMods [][]string
t, subMods, err = getTplDeep(root, fs, file, "", t)
2013-09-26 10:33:01 +00:00
if err != nil {
return nil, err
}
t, err = _getTemplate(t, root, fs, subMods, others...)
2013-09-26 10:33:01 +00:00
if err != nil {
return nil, err
}
return
}
func _getTemplate(t0 *template.Template, root string, fs http.FileSystem, subMods [][]string, others ...string) (t *template.Template, err error) {
2013-09-26 10:33:01 +00:00
t = t0
for _, m := range subMods {
2013-09-13 09:41:31 +00:00
if len(m) == 2 {
tpl := t.Lookup(m[1])
if tpl != nil {
2013-09-13 09:41:31 +00:00
continue
}
//first check filename
for _, otherFile := range others {
if otherFile == m[1] {
var subMods1 [][]string
t, subMods1, err = getTplDeep(root, fs, otherFile, "", t)
2013-09-13 09:41:31 +00:00
if err != nil {
logs.Trace("template parse file err:", err)
2017-03-17 17:24:45 +00:00
} else if len(subMods1) > 0 {
t, err = _getTemplate(t, root, fs, subMods1, others...)
2013-09-13 09:41:31 +00:00
}
break
}
}
//second check define
for _, otherFile := range others {
2017-04-28 14:36:28 +00:00
var data []byte
fileAbsPath := filepath.Join(root, otherFile)
f, err := fs.Open(fileAbsPath)
2013-09-13 09:41:31 +00:00
if err != nil {
2018-11-05 16:51:20 +00:00
f.Close()
logs.Trace("template file parse error, not success open file:", err)
2013-09-13 09:41:31 +00:00
continue
}
data, err = ioutil.ReadAll(f)
2018-11-05 16:51:20 +00:00
f.Close()
if err != nil {
logs.Trace("template file parse error, not success read file:", err)
continue
}
2015-12-09 15:35:04 +00:00
reg := regexp.MustCompile(BConfig.WebConfig.TemplateLeft + "[ ]*define[ ]+\"([^\"]+)\"")
allSub := reg.FindAllStringSubmatch(string(data), -1)
for _, sub := range allSub {
2013-09-13 09:41:31 +00:00
if len(sub) == 2 && sub[1] == m[1] {
var subMods1 [][]string
t, subMods1, err = getTplDeep(root, fs, otherFile, "", t)
2013-09-13 09:41:31 +00:00
if err != nil {
logs.Trace("template parse file err:", err)
2017-03-17 17:24:45 +00:00
} else if len(subMods1) > 0 {
t, err = _getTemplate(t, root, fs, subMods1, others...)
if err != nil {
logs.Trace("template parse file err:", err)
}
2013-09-13 09:41:31 +00:00
}
break
}
}
}
2013-09-13 05:57:40 +00:00
}
2013-09-13 09:41:31 +00:00
2013-09-13 05:57:40 +00:00
}
2013-09-12 09:20:32 +00:00
return
}
type templateFSFunc func() http.FileSystem
func defaultFSFunc() http.FileSystem {
return FileSystem{}
}
// SetTemplateFSFunc set default filesystem function
func SetTemplateFSFunc(fnt templateFSFunc) {
beeTemplateFS = fnt
}
// SetViewsPath sets view directory path in beego application.
func SetViewsPath(path string) *App {
2015-12-09 15:35:04 +00:00
BConfig.WebConfig.ViewsPath = path
return BeeApp
}
// SetStaticPath sets static directory path and proper url pattern in beego application.
// if beego.SetStaticPath("static","public"), visit /static/* to load static file in folder "public".
func SetStaticPath(url string, path string) *App {
if !strings.HasPrefix(url, "/") {
url = "/" + url
}
2016-02-02 04:43:45 +00:00
if url != "/" {
url = strings.TrimRight(url, "/")
}
2015-12-09 15:35:04 +00:00
BConfig.WebConfig.StaticDir[url] = path
return BeeApp
}
// DelStaticPath removes the static folder setting in this url pattern in beego application.
func DelStaticPath(url string) *App {
if !strings.HasPrefix(url, "/") {
url = "/" + url
}
2016-02-02 04:43:45 +00:00
if url != "/" {
url = strings.TrimRight(url, "/")
}
2015-12-09 15:35:04 +00:00
delete(BConfig.WebConfig.StaticDir, url)
return BeeApp
}
2017-04-29 01:13:28 +00:00
// AddTemplateEngine add a new templatePreProcessor which support extension
func AddTemplateEngine(extension string, fn templatePreProcessor) *App {
AddTemplateExt(extension)
beeTemplateEngines[extension] = fn
return BeeApp
}