github.com/astaxie/beego@v1.12.3/template.go (about) 1 // Copyright 2014 beego Author. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package beego 16 17 import ( 18 "errors" 19 "fmt" 20 "html/template" 21 "io" 22 "io/ioutil" 23 "net/http" 24 "os" 25 "path/filepath" 26 "regexp" 27 "strings" 28 "sync" 29 30 "github.com/astaxie/beego/logs" 31 "github.com/astaxie/beego/utils" 32 ) 33 34 var ( 35 beegoTplFuncMap = make(template.FuncMap) 36 beeViewPathTemplateLocked = false 37 // beeViewPathTemplates caching map and supported template file extensions per view 38 beeViewPathTemplates = make(map[string]map[string]*template.Template) 39 templatesLock sync.RWMutex 40 // beeTemplateExt stores the template extension which will build 41 beeTemplateExt = []string{"tpl", "html", "gohtml"} 42 // beeTemplatePreprocessors stores associations of extension -> preprocessor handler 43 beeTemplateEngines = map[string]templatePreProcessor{} 44 beeTemplateFS = defaultFSFunc 45 ) 46 47 // ExecuteTemplate applies the template with name to the specified data object, 48 // writing the output to wr. 49 // A template will be executed safely in parallel. 50 func ExecuteTemplate(wr io.Writer, name string, data interface{}) error { 51 return ExecuteViewPathTemplate(wr, name, BConfig.WebConfig.ViewsPath, data) 52 } 53 54 // ExecuteViewPathTemplate applies the template with name and from specific viewPath to the specified data object, 55 // writing the output to wr. 56 // A template will be executed safely in parallel. 57 func ExecuteViewPathTemplate(wr io.Writer, name string, viewPath string, data interface{}) error { 58 if BConfig.RunMode == DEV { 59 templatesLock.RLock() 60 defer templatesLock.RUnlock() 61 } 62 if beeTemplates, ok := beeViewPathTemplates[viewPath]; ok { 63 if t, ok := beeTemplates[name]; ok { 64 var err error 65 if t.Lookup(name) != nil { 66 err = t.ExecuteTemplate(wr, name, data) 67 } else { 68 err = t.Execute(wr, data) 69 } 70 if err != nil { 71 logs.Trace("template Execute err:", err) 72 } 73 return err 74 } 75 panic("can't find templatefile in the path:" + viewPath + "/" + name) 76 } 77 panic("Unknown view path:" + viewPath) 78 } 79 80 func init() { 81 beegoTplFuncMap["dateformat"] = DateFormat 82 beegoTplFuncMap["date"] = Date 83 beegoTplFuncMap["compare"] = Compare 84 beegoTplFuncMap["compare_not"] = CompareNot 85 beegoTplFuncMap["not_nil"] = NotNil 86 beegoTplFuncMap["not_null"] = NotNil 87 beegoTplFuncMap["substr"] = Substr 88 beegoTplFuncMap["html2str"] = HTML2str 89 beegoTplFuncMap["str2html"] = Str2html 90 beegoTplFuncMap["htmlquote"] = Htmlquote 91 beegoTplFuncMap["htmlunquote"] = Htmlunquote 92 beegoTplFuncMap["renderform"] = RenderForm 93 beegoTplFuncMap["assets_js"] = AssetsJs 94 beegoTplFuncMap["assets_css"] = AssetsCSS 95 beegoTplFuncMap["config"] = GetConfig 96 beegoTplFuncMap["map_get"] = MapGet 97 98 // Comparisons 99 beegoTplFuncMap["eq"] = eq // == 100 beegoTplFuncMap["ge"] = ge // >= 101 beegoTplFuncMap["gt"] = gt // > 102 beegoTplFuncMap["le"] = le // <= 103 beegoTplFuncMap["lt"] = lt // < 104 beegoTplFuncMap["ne"] = ne // != 105 106 beegoTplFuncMap["urlfor"] = URLFor // build a URL to match a Controller and it's method 107 } 108 109 // AddFuncMap let user to register a func in the template. 110 func AddFuncMap(key string, fn interface{}) error { 111 beegoTplFuncMap[key] = fn 112 return nil 113 } 114 115 type templatePreProcessor func(root, path string, funcs template.FuncMap) (*template.Template, error) 116 117 type templateFile struct { 118 root string 119 files map[string][]string 120 } 121 122 // visit will make the paths into two part,the first is subDir (without tf.root),the second is full path(without tf.root). 123 // if tf.root="views" and 124 // paths is "views/errors/404.html",the subDir will be "errors",the file will be "errors/404.html" 125 // paths is "views/admin/errors/404.html",the subDir will be "admin/errors",the file will be "admin/errors/404.html" 126 func (tf *templateFile) visit(paths string, f os.FileInfo, err error) error { 127 if f == nil { 128 return err 129 } 130 if f.IsDir() || (f.Mode()&os.ModeSymlink) > 0 { 131 return nil 132 } 133 if !HasTemplateExt(paths) { 134 return nil 135 } 136 137 replace := strings.NewReplacer("\\", "/") 138 file := strings.TrimLeft(replace.Replace(paths[len(tf.root):]), "/") 139 subDir := filepath.Dir(file) 140 141 tf.files[subDir] = append(tf.files[subDir], file) 142 return nil 143 } 144 145 // HasTemplateExt return this path contains supported template extension of beego or not. 146 func HasTemplateExt(paths string) bool { 147 for _, v := range beeTemplateExt { 148 if strings.HasSuffix(paths, "."+v) { 149 return true 150 } 151 } 152 return false 153 } 154 155 // AddTemplateExt add new extension for template. 156 func AddTemplateExt(ext string) { 157 for _, v := range beeTemplateExt { 158 if v == ext { 159 return 160 } 161 } 162 beeTemplateExt = append(beeTemplateExt, ext) 163 } 164 165 // AddViewPath adds a new path to the supported view paths. 166 //Can later be used by setting a controller ViewPath to this folder 167 //will panic if called after beego.Run() 168 func AddViewPath(viewPath string) error { 169 if beeViewPathTemplateLocked { 170 if _, exist := beeViewPathTemplates[viewPath]; exist { 171 return nil //Ignore if viewpath already exists 172 } 173 panic("Can not add new view paths after beego.Run()") 174 } 175 beeViewPathTemplates[viewPath] = make(map[string]*template.Template) 176 return BuildTemplate(viewPath) 177 } 178 179 func lockViewPaths() { 180 beeViewPathTemplateLocked = true 181 } 182 183 // BuildTemplate will build all template files in a directory. 184 // it makes beego can render any template file in view directory. 185 func BuildTemplate(dir string, files ...string) error { 186 var err error 187 fs := beeTemplateFS() 188 f, err := fs.Open(dir) 189 if err != nil { 190 if os.IsNotExist(err) { 191 return nil 192 } 193 return errors.New("dir open err") 194 } 195 defer f.Close() 196 197 beeTemplates, ok := beeViewPathTemplates[dir] 198 if !ok { 199 panic("Unknown view path: " + dir) 200 } 201 self := &templateFile{ 202 root: dir, 203 files: make(map[string][]string), 204 } 205 err = Walk(fs, dir, func(path string, f os.FileInfo, err error) error { 206 return self.visit(path, f, err) 207 }) 208 if err != nil { 209 fmt.Printf("Walk() returned %v\n", err) 210 return err 211 } 212 buildAllFiles := len(files) == 0 213 for _, v := range self.files { 214 for _, file := range v { 215 if buildAllFiles || utils.InSlice(file, files) { 216 templatesLock.Lock() 217 ext := filepath.Ext(file) 218 var t *template.Template 219 if len(ext) == 0 { 220 t, err = getTemplate(self.root, fs, file, v...) 221 } else if fn, ok := beeTemplateEngines[ext[1:]]; ok { 222 t, err = fn(self.root, file, beegoTplFuncMap) 223 } else { 224 t, err = getTemplate(self.root, fs, file, v...) 225 } 226 if err != nil { 227 logs.Error("parse template err:", file, err) 228 templatesLock.Unlock() 229 return err 230 } 231 beeTemplates[file] = t 232 templatesLock.Unlock() 233 } 234 } 235 } 236 return nil 237 } 238 239 func getTplDeep(root string, fs http.FileSystem, file string, parent string, t *template.Template) (*template.Template, [][]string, error) { 240 var fileAbsPath string 241 var rParent string 242 var err error 243 if strings.HasPrefix(file, "../") { 244 rParent = filepath.Join(filepath.Dir(parent), file) 245 fileAbsPath = filepath.Join(root, filepath.Dir(parent), file) 246 } else { 247 rParent = file 248 fileAbsPath = filepath.Join(root, file) 249 } 250 f, err := fs.Open(fileAbsPath) 251 if err != nil { 252 panic("can't find template file:" + file) 253 } 254 defer f.Close() 255 data, err := ioutil.ReadAll(f) 256 if err != nil { 257 return nil, [][]string{}, err 258 } 259 t, err = t.New(file).Parse(string(data)) 260 if err != nil { 261 return nil, [][]string{}, err 262 } 263 reg := regexp.MustCompile(BConfig.WebConfig.TemplateLeft + "[ ]*template[ ]+\"([^\"]+)\"") 264 allSub := reg.FindAllStringSubmatch(string(data), -1) 265 for _, m := range allSub { 266 if len(m) == 2 { 267 tl := t.Lookup(m[1]) 268 if tl != nil { 269 continue 270 } 271 if !HasTemplateExt(m[1]) { 272 continue 273 } 274 _, _, err = getTplDeep(root, fs, m[1], rParent, t) 275 if err != nil { 276 return nil, [][]string{}, err 277 } 278 } 279 } 280 return t, allSub, nil 281 } 282 283 func getTemplate(root string, fs http.FileSystem, file string, others ...string) (t *template.Template, err error) { 284 t = template.New(file).Delims(BConfig.WebConfig.TemplateLeft, BConfig.WebConfig.TemplateRight).Funcs(beegoTplFuncMap) 285 var subMods [][]string 286 t, subMods, err = getTplDeep(root, fs, file, "", t) 287 if err != nil { 288 return nil, err 289 } 290 t, err = _getTemplate(t, root, fs, subMods, others...) 291 292 if err != nil { 293 return nil, err 294 } 295 return 296 } 297 298 func _getTemplate(t0 *template.Template, root string, fs http.FileSystem, subMods [][]string, others ...string) (t *template.Template, err error) { 299 t = t0 300 for _, m := range subMods { 301 if len(m) == 2 { 302 tpl := t.Lookup(m[1]) 303 if tpl != nil { 304 continue 305 } 306 //first check filename 307 for _, otherFile := range others { 308 if otherFile == m[1] { 309 var subMods1 [][]string 310 t, subMods1, err = getTplDeep(root, fs, otherFile, "", t) 311 if err != nil { 312 logs.Trace("template parse file err:", err) 313 } else if len(subMods1) > 0 { 314 t, err = _getTemplate(t, root, fs, subMods1, others...) 315 } 316 break 317 } 318 } 319 //second check define 320 for _, otherFile := range others { 321 var data []byte 322 fileAbsPath := filepath.Join(root, otherFile) 323 f, err := fs.Open(fileAbsPath) 324 if err != nil { 325 f.Close() 326 logs.Trace("template file parse error, not success open file:", err) 327 continue 328 } 329 data, err = ioutil.ReadAll(f) 330 f.Close() 331 if err != nil { 332 logs.Trace("template file parse error, not success read file:", err) 333 continue 334 } 335 reg := regexp.MustCompile(BConfig.WebConfig.TemplateLeft + "[ ]*define[ ]+\"([^\"]+)\"") 336 allSub := reg.FindAllStringSubmatch(string(data), -1) 337 for _, sub := range allSub { 338 if len(sub) == 2 && sub[1] == m[1] { 339 var subMods1 [][]string 340 t, subMods1, err = getTplDeep(root, fs, otherFile, "", t) 341 if err != nil { 342 logs.Trace("template parse file err:", err) 343 } else if len(subMods1) > 0 { 344 t, err = _getTemplate(t, root, fs, subMods1, others...) 345 if err != nil { 346 logs.Trace("template parse file err:", err) 347 } 348 } 349 break 350 } 351 } 352 } 353 } 354 355 } 356 return 357 } 358 359 type templateFSFunc func() http.FileSystem 360 361 func defaultFSFunc() http.FileSystem { 362 return FileSystem{} 363 } 364 365 // SetTemplateFSFunc set default filesystem function 366 func SetTemplateFSFunc(fnt templateFSFunc) { 367 beeTemplateFS = fnt 368 } 369 370 // SetViewsPath sets view directory path in beego application. 371 func SetViewsPath(path string) *App { 372 BConfig.WebConfig.ViewsPath = path 373 return BeeApp 374 } 375 376 // SetStaticPath sets static directory path and proper url pattern in beego application. 377 // if beego.SetStaticPath("static","public"), visit /static/* to load static file in folder "public". 378 func SetStaticPath(url string, path string) *App { 379 if !strings.HasPrefix(url, "/") { 380 url = "/" + url 381 } 382 if url != "/" { 383 url = strings.TrimRight(url, "/") 384 } 385 BConfig.WebConfig.StaticDir[url] = path 386 return BeeApp 387 } 388 389 // DelStaticPath removes the static folder setting in this url pattern in beego application. 390 func DelStaticPath(url string) *App { 391 if !strings.HasPrefix(url, "/") { 392 url = "/" + url 393 } 394 if url != "/" { 395 url = strings.TrimRight(url, "/") 396 } 397 delete(BConfig.WebConfig.StaticDir, url) 398 return BeeApp 399 } 400 401 // AddTemplateEngine add a new templatePreProcessor which support extension 402 func AddTemplateEngine(extension string, fn templatePreProcessor) *App { 403 AddTemplateExt(extension) 404 beeTemplateEngines[extension] = fn 405 return BeeApp 406 }