不引入liquid支持库,借助原有的html/template来支持include子模板功能。
思路:
- 模仿Django,默认从templates文件夹下读取模板,已经取到的模板加入到map结构的简单cache中缓存
- 利用正则表达式在模板中发现 {% include xxx %}时,将其作为一个文件递归加载到当前模板中来,如果加载失败(如文件不存在等), 那么将其保持原样
- 利用html/template完成变量等的载入
规定格式时不能使用{{}}这种,因为template会认为这是一条它支持的指令,一旦在第2步中加载失败,就会保持原样,继续传递到template parser中去。
另外需要设置一个观察值,在进行递归include时防止出现递归包含自己的死循环。
代码实现:
func RenderWithTemplate(writer io.Writer, templateName string, data map[string]interface{}) error {
rawTemplate, err := LoadTemplate(templateName)
if err != nil {
return err
}
tmpl, err := template.New("new").Parse(rawTemplate)
if err != nil {
return err
}
return tmpl.Execute(writer, data)
}
var TemplateCache map[string]string = nil
var gRecursiveLoadTemplateCount = 0
// Load template with file name templateName under 'templates' dir
// If it contains {% include xxx %}, it will load content of that
// template into current template.
// **Note**: template name inside include directive should not be wrapped with ''
func LoadTemplate(templateName string) (string, error) {
if result, ok := TemplateCache[templateName]; ok {
return result, nil
}
templatePath := "templates/" + templateName
file, err := os.Open(templatePath)
if err != nil {
return "", err
}
stat, err := file.Stat()
if err != nil {
return "", err
}
buffer := make([]byte, stat.Size())
_, err = file.Read(buffer)
if TemplateCache == nil {
TemplateCache = make(map[string]string)
}
rawTemplate := string(buffer)
re := regexp.MustCompile("{%\\s*include\\s+(.*?)\\s*%}")
matches := re.FindAllStringSubmatchIndex(rawTemplate, -1)
if len(matches) > 0 {
newTemplate := ""
start := 0
for _, m := range matches {
if len(m) == 4 {
newTemplate += rawTemplate[start:m[0]]
includeName := rawTemplate[m[2]:m[3]]
gRecursiveLoadTemplateCount ++
if gRecursiveLoadTemplateCount > 10 {
log.Fatal("Possible dead include loop in file: ", templateName)
}
includedTemplate, err := LoadTemplate(includeName)
gRecursiveLoadTemplateCount --
if err != nil {
fmt.Println("Failed to include file ", includeName, " from ", templateName, "err:", err)
includedTemplate = rawTemplate[m[0]:m[1]] // Keep it as original
}
start = m[1]
newTemplate += includedTemplate
}
}
if start < len(rawTemplate) {
newTemplate += rawTemplate[start:]
}
rawTemplate = newTemplate
}
TemplateCache[templateName] = rawTemplate
return rawTemplate, nil
}