text/template问题Parse()与ParseFiles()的区别

huangapple go评论190阅读模式
英文:

text/template issue Parse() vs. ParseFiles()

问题

我正在尝试使用text/template包做一些简单的工作。我正在使用template顶部给出的示例。

我如何编写“parsed”文件,以便template.ParseFiles()可以正确读取和执行它?

  1. package main
  2. import (
  3. "text/template"
  4. "os"
  5. )
  6. type Inventory struct {
  7. Material string
  8. Count uint
  9. }
  10. func main() {
  11. sweaters := Inventory{"wool", 17}
  12. tmpl, err := template.New("test").Parse("{{.Count}} items are made of {{.Material}}")
  13. // tmpl, err := template.New("test").ParseFiles("file.txt")
  14. if err != nil { panic(err) }
  15. err = tmpl.Execute(os.Stdout, sweaters)
  16. if err != nil { panic(err) }
  17. }

文件.txt的内容:

  1. {{.Count}} items are made of {{.Material}}

抛出的错误:

  1. panic: template: test:1: "test" is an incomplete or empty template
  2. goroutine 1 [running]:
  3. main.main()
  4. /tmp/templates/t.go:19 +0x21a
  5. goroutine 2 [syscall]:
  6. created by runtime.main
  7. /var/tmp/portage/dev-lang/go-1.0.1/work/go/src/pkg/runtime/proc.c:221

我在golang playground上发布了这段代码的副本这里

编辑#1:
我对这个问题进行了一些研究...由于实际上是Execute()方法抛出异常,而不是ParseFiles()部分,所以我检查了方法定义:

  1. // Execute applies a parsed template to the specified data object,
  2. // and writes the output to wr.
  3. func (t *Template) Execute(wr io.Writer, data interface{}) (err error) {
  4. defer errRecover(&err)
  5. value := reflect.ValueOf(data)
  6. state := &state{
  7. tmpl: t,
  8. wr: wr,
  9. line: 1,
  10. vars: []variable{{"$", value}},
  11. }
  12. if t.Tree == nil || t.Root == nil {
  13. state.errorf("%q is an incomplete or empty template", t.name)
  14. }
  15. state.walk(value, t.Root)
  16. return
  17. }

所以,凭直觉,我输出了内联的“非文件”样式的t.Tree的值,tmpl是:&parse.Tree{Name:"test", Root:(*parse.ListNode)(0xf840030700), funcs:[]map[string]interface {}(nil), lex:(*parse.lexer)(nil), token:[2]parse.item{parse.item{typ:6, val:""}, parse.item{typ:9, val:"{{"}}, peekCount:1, vars:[]string(nil)},当使用ParseFiles()运行时,tmpl是:(*parse.Tree)(nil)。我发现一个是解引用,一个是指针。这可能有助于解决这个谜题。

英文:

I'm trying to do some simple work with the text/template package. The sample given at the top of template is what I'm working with.

How do I write the 'parsed' file so template.ParseFiles() properly reads and executes it?

  1. package main
  2. import (
  3. "text/template"
  4. "os"
  5. )
  6. type Inventory struct {
  7. Material string
  8. Count uint
  9. }
  10. func main() {
  11. sweaters := Inventory{"wool", 17}
  12. tmpl, err := template.New("test").Parse("{{.Count}} items are made of {{.Material}}")
  13. // tmpl, err := template.New("test").ParseFiles("file.txt")
  14. if err != nil { panic(err) }
  15. err = tmpl.Execute(os.Stdout, sweaters)
  16. if err != nil { panic(err) }
  17. }
  18. /*
  19. Contents of file.txt:
  20. {{.Count}} items are made of {{.Material}}
  21. Error thrown:
  22. panic: template: test:1: "test" is an incomplete or empty template
  23. goroutine 1 [running]:
  24. main.main()
  25. /tmp/templates/t.go:19 +0x21a
  26. goroutine 2 [syscall]:
  27. created by runtime.main
  28. /var/tmp/portage/dev-lang/go-1.0.1/work/go/src/pkg/runtime/proc.c:221
  29. */

I have a copy of this code posted at the golang playground here

Edit #1:
I've been doing some research on this issue... since it's the Execute() method that actually throws the exception, and not the ParseFiles() part, I checked the method definition:

  1. // Execute applies a parsed template to the specified data object,
  2. // and writes the output to wr.
  3. func (t *Template) Execute(wr io.Writer, data interface{}) (err error) {
  4. defer errRecover(&err)
  5. value := reflect.ValueOf(data)
  6. state := &state{
  7. tmpl: t,
  8. wr: wr,
  9. line: 1,
  10. vars: []variable{{"$", value}},
  11. }
  12. if t.Tree == nil || t.Root == nil {
  13. state.errorf("%q is an incomplete or empty template", t.name)
  14. }
  15. state.walk(value, t.Root)
  16. return
  17. }

So, on a hunch, I dumped the value of t.Tree for the inline 'non-file' style, tmpl is: &parse.Tree{Name:"test", Root:(*parse.ListNode)(0xf840030700), funcs:[]map[string]interface {}(nil), lex:(*parse.lexer)(nil), token:[2]parse.item{parse.item{typ:6, val:""}, parse.item{typ:9, val:"{{"}}, peekCount:1, vars:[]string(nil)} and
when ran with ParseFiles(), tmpl is: (*parse.Tree)(nil). I find it odd that one is a dereference, and one value is a pointer. This may help solve the riddle

答案1

得分: 17

sweaters := Inventory{"wool", 17}
tmpl, err := template.ParseFiles("file.txt")
if err != nil {
panic(err)
}
err = tmpl.ExecuteTemplate(os.Stdout, "file.txt", sweaters)
if err != nil {
panic(err)
}

如果你有很多文件,你可以使用ParseGlob:

tmpl, err := template.ParseGlob("*.txt")
if err != nil {
panic(err)
}
err = tmpl.ExecuteTemplate(os.Stdout, "file.txt", sweaters)
if err != nil {
panic(err)
}
err = tmpl.ExecuteTemplate(os.Stdout, "file2.txt", sweaters)
if err != nil {
panic(err)
}

英文:
  1. sweaters := Inventory{"wool", 17}
  2. tmpl, err := template.ParseFiles("file.txt")
  3. if err != nil {
  4. panic(err)
  5. }
  6. err = tmpl.ExecuteTemplate(os.Stdout, "file.txt", sweaters)
  7. if err != nil {
  8. panic(err)
  9. }

If you have many files, you can use ParseGlob:

  1. tmpl, err := template.ParseGlob("*.txt")
  2. if err != nil {
  3. panic(err)
  4. }
  5. err = tmpl.ExecuteTemplate(os.Stdout, "file.txt", sweaters)
  6. if err != nil {
  7. panic(err)
  8. }
  9. err = tmpl.ExecuteTemplate(os.Stdout, "file2.txt", sweaters)
  10. if err != nil {
  11. panic(err)
  12. }

答案2

得分: 11

在Go模板parseFiles中有一个小技巧。

只有与相同名称的模板才会被重用,否则会创建一个新的模板。
就像你的示例一样:

tmpl, err := template.New("test").ParseFiles("file.txt")

tmpl是名为"test"的模板,并关联另一个名为"file.txt"的模板,你在"test"模板上调用Execute时,这个模板是一个空模板,所以会引发错误"test is an incomplete or empty template"。

当你将模板名称更改为file.txt时,它就可以工作了。

tmpl, err := template.New("file.txt").ParseFiles("file.txt")

英文:

There is a little trick in Go template parseFiles.

  1. func parseFiles(t *Template, filenames ...string) (*Template, error) {
  2. if len(filenames) == 0 {
  3. // Not really a problem, but be consistent.
  4. return nil, fmt.Errorf("template: no files named in call to ParseFiles")
  5. }
  6. for _, filename := range filenames {
  7. b, err := ioutil.ReadFile(filename)
  8. if err != nil {
  9. return nil, err
  10. }
  11. s := string(b)
  12. name := filepath.Base(filename)
  13. // First template becomes return value if not already defined,
  14. // and we use that one for subsequent New calls to associate
  15. // all the templates together. Also, if this file has the same name
  16. // as t, this file becomes the contents of t, so
  17. // t, err := New(name).Funcs(xxx).ParseFiles(name)
  18. // works. Otherwise we create a new template associated with t.
  19. var tmpl *Template
  20. if t == nil {
  21. t = New(name)
  22. }
  23. if name == t.Name() {
  24. tmpl = t
  25. } else {
  26. tmpl = t.New(name)
  27. }
  28. _, err = tmpl.Parse(s)
  29. if err != nil {
  30. return nil, err
  31. }
  32. }
  33. return t, nil
  34. }

Only the template with same name will be reuse, otherwise create new one.
as your sample:

  1. tmpl, err := template.New("test").ParseFiles("file.txt")

tmpl is the template named "test", and associated another template named "file.txt", you call Execute on "test" template, this template is a empty template, so raise the error "test is an incomplete or empty template".

It worked when you change the template name to file.txt

  1. tmpl, err := template.New("file.txt").ParseFiles("file.txt")

huangapple
  • 本文由 发表于 2012年8月4日 10:04:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/11805356.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定