GO:借助html/template实现include子模板

不引入liquid支持库,借助原有的html/template来支持include子模板功能。

思路:

  1. 模仿Django,默认从templates文件夹下读取模板,已经取到的模板加入到map结构的简单cache中缓存
  2. 利用正则表达式在模板中发现 {% include xxx %}时,将其作为一个文件递归加载到当前模板中来,如果加载失败(如文件不存在等), 那么将其保持原样
  3. 利用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
}

你可能感兴趣的:(GO:借助html/template实现include子模板)