Created
April 20, 2013 12:07
-
-
Save reusee/5425786 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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