Skip to content

Instantly share code, notes, and snippets.

@reusee
Created April 20, 2013 12:07
Show Gist options
  • Select an option

  • Save reusee/5425786 to your computer and use it in GitHub Desktop.

Select an option

Save reusee/5425786 to your computer and use it in GitHub Desktop.
package main
import (
"encoding/xml"
"log"
"os"
"io"
"fmt"
"time"
"strings"
"regexp"
"runtime/pprof"
)
func init() {
fmt.Printf("")
}
func main() {
template, err := LoadTemplateString(`
<div>
<h1 class='header'>foo {{.Header}} bar baz {{.Header}} quux</h1>
<h2 class='header2'>{{.Header2}}</h2>
<h3 class='header3'>{{.Header3}}</h3>
<h4 class='header4'>{{.Header4}}</h4>
<h5 class='header5'>{{.Header5}}</h5>
<h6 class='header6'>{{.Header6}}</h6>
<ul class='list'>
{{range .List}}
<li class='item'>{{.}}</li>
{{end}}
</ul>
</div>
`)
if err != nil {
log.Fatalf("%v\n", err)
}
strs := map[string]string{
"Header": "Header",
"Header2": "Header2",
"Header3": "Header3",
"Header4": "Header4",
"Header5": "Header5",
"Header6": "Header6",
}
lists := map[string][]string {
"List": []string{
"foo", "bar", "baz",
},
}
start := time.Now()
f, _ := os.Create("prof")
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
for i := 0; i < 100000; i++ {
ret := template.Render(strs, lists)
_ = ret
//print(ret)
//print("\n")
}
fmt.Printf("%v\n", time.Now().Sub(start))
}
func LoadTemplateString(str string) (*Template, error) {
tokenizer := &Tokenizer{xml.NewDecoder(strings.NewReader(str)), make([]interface{}, 0, 1024),
regexp.MustCompile(`\{\{[^}]*\}\}`)}
return newTemplate(tokenizer)
}
func LoadTemplateFile(filePath string) (*Template, error) {
in, err := os.Open(filePath)
if err != nil {
return nil, err
}
tokenizer := &Tokenizer{xml.NewDecoder(in), make([]interface{}, 0, 1024),
regexp.MustCompile(`\{\{[^}]*\}\}`)}
return newTemplate(tokenizer)
}
type Tokenizer struct {
*xml.Decoder
queue []interface{}
re *regexp.Regexp
}
func (self *Tokenizer) next() (ret interface{}, err error) {
if len(self.queue) > 0 {
ret = self.queue[0]
self.queue = self.queue[1:]
return
}
ret, err = self.Token()
switch elem := ret.(type) {
case xml.CharData:
subs := self.extract(string(elem))
ret = subs[0]
self.queue = append(self.queue, subs[1:]...)
default:
}
return
}
func (self *Tokenizer) extract(str string) []interface{} {
ret := make([]interface{}, 0, 64)
indexes := self.re.FindAllStringIndex(str, -1)
startIndex := 0
for _, index := range(indexes) {
if index[0] == startIndex {
ret = append(ret, xml.CharData(str[index[0] : index[1]]))
} else {
ret = append(ret, xml.CharData(str[startIndex : index[0]]))
ret = append(ret, xml.CharData(str[index[0] : index[1]]))
}
startIndex = index[1]
}
if startIndex < len(str) {
ret = append(ret, xml.CharData(str[startIndex : len(str)]))
}
return ret
}
func newTemplate(tokenizer *Tokenizer) (*Template, error) {
var token xml.Token
var err error
template := new(Template)
template.elems = make([]Renderable, 0, 1024)
buf := make([]string, 0, 1024)
loop: for {
token, err = tokenizer.next()
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
switch elem := token.(type) {
case xml.StartElement:
buf = append(buf, "<" + elem.Name.Local)
for _, attr := range(elem.Attr) {
buf = append(buf, " " + attr.Name.Local + "=\"" + attr.Value + "\"")
}
buf = append(buf, ">")
case xml.EndElement:
buf = append(buf, "</" + elem.Name.Local + ">")
case xml.CharData:
str := string(elem)
if strings.HasPrefix(str, "{{.") && strings.HasSuffix(str, "}}") {
// string var
template.elems = append(template.elems, Text(strings.Join(buf, "")))
buf = buf[0:0]
template.elems = append(template.elems, String(str[3:len(str) - 2]))
} else if strings.HasPrefix(str, "{{range .") && strings.HasSuffix(str, "}}") {
// list var
template.elems = append(template.elems, Text(strings.Join(buf, "")))
buf = buf[0:0]
subTemplate, err := newTemplate(tokenizer)
if err != nil {
return nil, err
}
template.elems = append(template.elems, List{str[9:len(str) - 2], subTemplate})
} else if str == "{{end}}" {
break loop
} else {
buf = append(buf, str)
}
default:
}
}
if len(buf) > 0 {
template.elems = append(template.elems, Text(strings.Join(buf, "")))
}
return template, nil
}
type Template struct {
elems []Renderable
}
func (self *Template) Render(strs map[string]string, lists map[string][]string) (ret string) {
buf := make([]string, 0, 1024)
for _, elem := range(self.elems) {
buf = append(buf, elem.Render(strs, lists))
}
ret = strings.Join(buf, "")
return
}
type Text string
func (self Text) Render(map[string]string, map[string][]string) string {
return string(self)
}
type String string
func (self String) Render(strs map[string]string, lists map[string][]string) string {
return strs[string(self)]
}
type List struct {
key string
template *Template
}
func (self List) Render(strs map[string]string, lists map[string][]string) string {
buf := make([]string, 0, 128)
newStrs := make(map[string]string)
for k, v := range(strs) {
newStrs[k] = v
}
for _, value := range(lists[self.key]) {
newStrs[""] = value
buf = append(buf, self.template.Render(newStrs, lists))
}
return strings.Join(buf, "")
}
type Renderable interface {
Render(map[string]string, map[string][]string) string
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment