英文:
How to get a map or list of template 'actions' from a parsed template?
问题
所以我想以某种方式将模板中所有的{{ .blahblah }}
操作作为字符串切片定义。
例如,如果我有以下模板:
<h1>{{ .name }} {{ .age }}</h1>
我想要能够得到[]string{"name", "age"}
。假设模板有一个方法func (t *Template) Fields() []string
:
t := template.New("cooltemplate").Parse(`<h1>{{ .name }} {{ .age }}</h1>`)
if t.Fields() == []string{"name", "age"} {
fmt.Println("Yay, 现在我知道可以传递哪些字段了!")
// 现在让我们传递刚刚发现的name字段。
_ = t.Execute(os.Stdout, map[string]string{"name": "Jack", "age":"120"})
}
有没有一种方法可以检查解析后的模板呢?
谢谢!
英文:
So I would like to somehow get all of my {{ .blahblah }}
actions defined in a template as slice of strings.
For example if I have this template:
<h1>{{ .name }} {{ .age }}</h1>
I would like to be able to get []string{"name", "age"}
. Pretend that a template has the method func (t *Template) Fields() []string
:
t := template.New("cooltemplate").Parse(`<h1>{{ .name }} {{ .age }}</h1>`)
if t.Fields() == []string{"name", "age"} {
fmt.Println("Yay, now I know what fields I can pass in!")
// Now lets pass in the name field that we just discovered.
_ = t.Execute(os.Stdout, map[string]string{"name": "Jack", "age":"120"})
}
Is there a way to inspect a parsed template like this?
Thanks!
答案1
得分: 7
前言:正如Voker建议的那样,Template.Tree
字段仅供html/template
使用,所有其他客户端都应将其视为未导出字段。
您不应依赖这种方法来为模板执行提供输入。您必须了解要执行的模板以及它所期望的数据。您不应在运行时“探索”它以提供参数。
解析模板后,您会得到一个template.Template
(无论是text/template
还是html/template
,它们具有相同的API)。此模板将模板表示为parse.Tree
类型的树。文本模板包含的所有内容都存储在这棵树中的节点中,包括静态文本、操作等。
话虽如此,您可以遍历此树并查找标识访问字段或调用函数的操作的节点。这些节点属于parse.Node
类型,它具有一个返回其类型的Node.Type()
方法。可能的类型在parse
包中定义为常量,与parse.NodeType
类型一起,例如:
const (
NodeText NodeType = iota // 普通文本。
NodeAction // 非控制操作,如字段评估。
NodeBool // 布尔常量。
NodeChain // 字段访问的序列。
NodeCommand // 流水线的元素。
NodeDot // 光标,点。
NodeField // 字段或方法名。
NodeIdentifier // 标识符;始终是函数名。
NodeIf // if操作。
NodeList // 节点列表。
NodeNil // 无类型的nil常量。
NodeNumber // 数值常量。
NodePipe // 命令的流水线。
NodeRange // range操作。
NodeString // 字符串常量。
NodeTemplate // 模板调用操作。
NodeVariable // $变量。
NodeWith // with操作。
)
下面是一个示例程序,它递归遍历模板树,并查找具有NodeAction
类型的节点,该类型是“非控制操作,如字段评估”。
此解决方案只是一个演示,一个概念验证,它不能处理所有情况。
func ListTemplFields(t *template.Template) []string {
return listNodeFields(t.Tree.Root, nil)
}
func listNodeFields(node parse.Node, res []string) []string {
if node.Type() == parse.NodeAction {
res = append(res, node.String())
}
if ln, ok := node.(*parse.ListNode); ok {
for _, n := range ln.Nodes {
res = listNodeFields(n, res)
}
}
return res
}
使用示例:
t := template.Must(template.New("cooltemplate").
Parse(`<h1>{{ .name }} {{ .age }}</h1>`))
fmt.Println(ListTemplFields(t))
输出(在Go Playground上尝试):
[{{.name}} {{.age}}]
英文:
Foreword: As Voker suggests, the Template.Tree
field is "exported only for use by html/template and should be treated as unexported by all other clients."
You should not rely on such thing to be able to provide input for a template execution. You must know the template you want to execute along with the data it expects. You shouldn't "explore" it at runtime to provide arguments for it.
The value you get out of parsing a template is template.Template
(either text/template
or html/template
, they have the same API). This template represents the templates as a tree of type parse.Tree
. Everything that a text template contains is stored in this tree in nodes, including static texts, actions etc.
Having said that, you can walk this tree and look for nodes that identify such actions that access fields or call functions. The nodes are of type parse.Node
which has a Node.Type()
method returning its type. The possible types are defined as constants in the parse
package, next to the parse.NodeType
type, e.g.
const (
NodeText NodeType = iota // Plain text.
NodeAction // A non-control action such as a field evaluation.
NodeBool // A boolean constant.
NodeChain // A sequence of field accesses.
NodeCommand // An element of a pipeline.
NodeDot // The cursor, dot.
NodeField // A field or method name.
NodeIdentifier // An identifier; always a function name.
NodeIf // An if action.
NodeList // A list of Nodes.
NodeNil // An untyped nil constant.
NodeNumber // A numerical constant.
NodePipe // A pipeline of commands.
NodeRange // A range action.
NodeString // A string constant.
NodeTemplate // A template invocation action.
NodeVariable // A $ variable.
NodeWith // A with action.
)
So here is an example program that recursively walks a template tree, and looks for nodes with NodeAction
type, which is "A non-control action such as a field evaluation."
This solution is just a demonstration, a proof of concept, it does not handle all cases.
func ListTemplFields(t *template.Template) []string {
return listNodeFields(t.Tree.Root, nil)
}
func listNodeFields(node parse.Node, res []string) []string {
if node.Type() == parse.NodeAction {
res = append(res, node.String())
}
if ln, ok := node.(*parse.ListNode); ok {
for _, n := range ln.Nodes {
res = listNodeFields(n, res)
}
}
return res
}
Example using it:
t := template.Must(template.New("cooltemplate").
Parse(`<h1>{{ .name }} {{ .age }}</h1>`))
fmt.Println(ListTemplFields(t))
Output (try it on the Go Playground):
[{{.name}} {{.age}}]
答案2
得分: 1
一个对 @icza 的答案的小优化,也许能有一点帮助
func listNodeFieldsV2(node parse.Node) []string {
var res []string
if node.Type() == parse.NodeAction {
res = append(res, node.String())
}
if ln, ok := node.(*parse.ListNode); ok {
for _, n := range ln.Nodes {
res = append(res, listNodeFieldsV2(n)...)
}
}
return res
}
英文:
A small optimization to @icza 's answer, maybe it helps a little
func listNodeFieldsV2(node parse.Node) []string {
var res []string
if node.Type() == parse.NodeAction {
res = append(res, node.String())
}
if ln, ok := node.(*parse.ListNode); ok {
for _, n := range ln.Nodes {
res = append(res, listNodeFieldsV2(n)...)
}
}
return res
}
答案3
得分: 0
我已经需要大致相同的代码了。
在我的用例中,我们允许用户在一侧创建模板,并在不同的表单中输入一个map[string]string
类型的变量,用于渲染。
这是我目前想出的代码(受到icza的答案的启发):
// 从*简单*模板中提取所需的模板变量。
// 仅适用于顶级的普通变量。返回所有有问题的parse.Node作为错误。
func RequiredTemplateVars(t *template.Template) ([]string, []error) {
var res []string
var errors []error
var ln *parse.ListNode
ln = t.Tree.Root
Node:
for _, n := range ln.Nodes {
if nn, ok := n.(*parse.ActionNode); ok {
p := nn.Pipe
if len(p.Decl) > 0 {
errors = append(errors, fmt.Errorf("不支持节点 %v", n))
continue Node
}
for _, c := range p.Cmds {
if len(c.Args) != 1 {
errors = append(errors, fmt.Errorf("不支持节点 %v", n))
continue Node
}
if a, ok := c.Args[0].(*parse.FieldNode); ok {
if len(a.Ident) != 1 {
errors = append(errors, fmt.Errorf("不支持节点 %v", n))
continue Node
}
res = append(res, a.Ident[0])
} else {
errors = append(errors, fmt.Errorf("不支持节点 %v", n))
continue Node
}
}
} else {
if _, ok := n.(*parse.TextNode); !ok {
errors = append(errors, fmt.Errorf("不支持节点 %v", n))
continue Node
}
}
}
return res, errors
}
你可以在这里查看代码:https://play.golang.org/p/nH95B45jUmI
英文:
I have happened to need roughly the same code.
In my use case, we allow users to create templates on one side, and to enter a map[string]string
of variables to be used for rendering in a different form.
This is the code that I came up with so far (inspired by icza's answer):
// Extract the template vars required from *simple* templates.
// Only works for top level, plain variables. Returns all problematic parse.Node as errors.
func RequiredTemplateVars(t *template.Template) ([]string, []error) {
var res []string
var errors []error
var ln *parse.ListNode
ln = t.Tree.Root
Node:
for _, n := range ln.Nodes {
if nn, ok := n.(*parse.ActionNode); ok {
p := nn.Pipe
if len(p.Decl) > 0 {
errors = append(errors, fmt.Errorf("Node %v not supported", n))
continue Node
}
for _, c := range p.Cmds {
if len(c.Args) != 1 {
errors = append(errors, fmt.Errorf("Node %v not supported", n))
continue Node
}
if a, ok := c.Args[0].(*parse.FieldNode); ok {
if len(a.Ident) != 1 {
errors = append(errors, fmt.Errorf("Node %v not supported", n))
continue Node
}
res = append(res, a.Ident[0])
} else {
errors = append(errors, fmt.Errorf("Node %v not supported", n))
continue Node
}
}
} else {
if _, ok := n.(*parse.TextNode); !ok {
errors = append(errors, fmt.Errorf("Node %v not supported", n))
continue Node
}
}
}
return res, errors
}
答案4
得分: 0
func parseTemplateVariables(template string) {
parsedTemplate, err := text_template.New("test").Option("missingkey=error").Parse(template)
if err != nil {
panic(err)
}
visited := make(map[interface{}]bool)
queue := []reflect.Value{reflect.ValueOf(parsedTemplate.Root)}
for len(queue) != 0 {
node := queue[0]
queue = queue[1:]
for node.Kind() != reflect.Struct && node.Kind() != reflect.Array && node.Kind() != reflect.Slice {
node = node.Elem()
}
if node.CanInterface() {
vInterface := node.Interface()
if listNode, ok := vInterface.([]parse.Node); ok {
for _, itemNode := range listNode {
if itemNode.Type() == parse.NodeField {
log.Printf("using variable: %v", itemNode)
}
}
}
}
var n int
if node.Kind() == reflect.Array || node.Kind() == reflect.Slice {
n = node.Len()
} else {
n = node.NumField()
}
for i := 0; i != n; i++ {
var itemNode reflect.Value
if node.Kind() == reflect.Array || node.Kind() == reflect.Slice {
itemNode = node.Index(i)
} else {
itemNode = node.Field(i)
}
if !itemNode.IsValid() || itemNode.IsZero() {
continue
}
if itemNode.Kind() == reflect.Bool ||
itemNode.Kind() == reflect.String ||
itemNode.Kind() == reflect.Int || itemNode.Kind() == reflect.Int8 || itemNode.Kind() == reflect.Int16 || itemNode.Kind() == reflect.Int32 || itemNode.Kind() == reflect.Int64 ||
itemNode.Kind() == reflect.Uint || itemNode.Kind() == reflect.Uint8 || itemNode.Kind() == reflect.Uint16 || itemNode.Kind() == reflect.Uint32 || itemNode.Kind() == reflect.Uint64 || itemNode.Kind() == reflect.Uintptr ||
itemNode.Kind() == reflect.Float32 || itemNode.Kind() == reflect.Float64 ||
itemNode.Kind() == reflect.Complex64 || itemNode.Kind() == reflect.Complex128 {
continue
}
k := itemNode.Kind()
_ = k
if itemNode.Kind() == reflect.Array || itemNode.Kind() == reflect.Slice {
itemNodePtr := itemNode.Addr()
if _, ok := visited[itemNodePtr]; ok {
continue
}
visited[itemNodePtr] = true
//log.Printf("%v", itemNodePtr)
} else {
itemNodePtr := itemNode.Addr()
if _, ok := visited[itemNodePtr]; ok {
continue
}
visited[itemNodePtr] = true
//log.Printf("%v", itemNodePtr)
}
queue = append(queue, itemNode)
}
}
}
这是一个使用反射和模板的解决方案,用于在模板中搜索变量。函数parseTemplateVariables
接受一个模板字符串作为参数。它首先解析模板,并将解析后的模板存储在parsedTemplate
变量中。然后,它使用反射遍历模板的节点,并检查节点类型是否为parse.NodeField
,如果是,则打印出相应的变量。该函数使用一个队列来存储待处理的节点,直到队列为空为止。在处理节点时,它会检查节点的类型,并根据类型进行相应的操作。如果节点是结构体、数组或切片类型,它会将节点的子节点添加到队列中以便后续处理。如果节点是基本类型(如布尔型、字符串型、整型等),则跳过该节点。该函数还使用一个visited
映射来避免处理重复的节点。
英文:
solution for search variables in template, with reflect + template
func parseTemplateVariables(template string) {
parsedTemplate, err := text_template.New("test").Option("missingkey=error").Parse(template)
if err != nil {
panic(err)
}
visited := make(map[interface{}]bool)
queue := []reflect.Value{reflect.ValueOf(parsedTemplate.Root)}
for len(queue) != 0 {
node := queue[0]
queue = queue[1:]
for node.Kind() != reflect.Struct && node.Kind() != reflect.Array && node.Kind() != reflect.Slice {
node = node.Elem()
}
if node.CanInterface() {
vInterface := node.Interface()
if listNode, ok := vInterface.([]parse.Node); ok {
for _, itemNode := range listNode {
if itemNode.Type() == parse.NodeField {
log.Printf("using variable: %v", itemNode)
}
}
}
}
var n int
if node.Kind() == reflect.Array || node.Kind() == reflect.Slice {
n = node.Len()
} else {
n = node.NumField()
}
for i := 0; i != n; i++ {
var itemNode reflect.Value
if node.Kind() == reflect.Array || node.Kind() == reflect.Slice {
itemNode = node.Index(i)
} else {
itemNode = node.Field(i)
}
if !itemNode.IsValid() || itemNode.IsZero() {
continue
}
if itemNode.Kind() == reflect.Bool ||
itemNode.Kind() == reflect.String ||
itemNode.Kind() == reflect.Int || itemNode.Kind() == reflect.Int8 || itemNode.Kind() == reflect.Int16 || itemNode.Kind() == reflect.Int32 || itemNode.Kind() == reflect.Int64 ||
itemNode.Kind() == reflect.Uint || itemNode.Kind() == reflect.Uint8 || itemNode.Kind() == reflect.Uint16 || itemNode.Kind() == reflect.Uint32 || itemNode.Kind() == reflect.Uint64 || itemNode.Kind() == reflect.Uintptr ||
itemNode.Kind() == reflect.Float32 || itemNode.Kind() == reflect.Float64 ||
itemNode.Kind() == reflect.Complex64 || itemNode.Kind() == reflect.Complex128 {
continue
}
k := itemNode.Kind()
_ = k
if itemNode.Kind() == reflect.Array || itemNode.Kind() == reflect.Slice {
itemNodePtr := itemNode.Addr()
if _, ok := visited[itemNodePtr]; ok {
continue
}
visited[itemNodePtr] = true
//log.Printf("%v", itemNodePtr)
} else {
itemNodePtr := itemNode.Addr()
if _, ok := visited[itemNodePtr]; ok {
continue
}
visited[itemNodePtr] = true
//log.Printf("%v", itemNodePtr)
}
queue = append(queue, itemNode)
}
}
}
答案5
得分: 0
func parseTemplateVariables(template string) {
parsedTemplate, err := text_template.New("test").Option("missingkey=error").Parse(template)
if err != nil {
panic(err)
}
visited := make(map[interface{}]bool)
queue := []reflect.Value{reflect.ValueOf(parsedTemplate.Root)}
for len(queue) != 0 {
node := queue[0]
queue = queue[1:]
for node.Kind() != reflect.Struct && node.Kind() != reflect.Array && node.Kind() != reflect.Slice {
node = node.Elem()
}
if node.CanInterface() {
vInterface := node.Interface()
if listNode, ok := vInterface.([]parse.Node); ok {
for _, itemNode := range listNode {
if itemNode.Type() == parse.NodeField {
log.Printf("using variable: %v", itemNode)
}
}
}
}
var n int
if node.Kind() == reflect.Array || node.Kind() == reflect.Slice {
n = node.Len()
} else {
n = node.NumField()
}
for i := 0; i != n; i++ {
var itemNode reflect.Value
if node.Kind() == reflect.Array || node.Kind() == reflect.Slice {
itemNode = node.Index(i)
} else {
itemNode = node.Field(i)
}
if !itemNode.IsValid() || itemNode.IsZero() {
continue
}
if itemNode.Kind() == reflect.Bool ||
itemNode.Kind() == reflect.String ||
itemNode.Kind() == reflect.Int || itemNode.Kind() == reflect.Int8 || itemNode.Kind() == reflect.Int16 || itemNode.Kind() == reflect.Int32 || itemNode.Kind() == reflect.Int64 ||
itemNode.Kind() == reflect.Uint || itemNode.Kind() == reflect.Uint8 || itemNode.Kind() == reflect.Uint16 || itemNode.Kind() == reflect.Uint32 || itemNode.Kind() == reflect.Uint64 || itemNode.Kind() == reflect.Uintptr ||
itemNode.Kind() == reflect.Float32 || itemNode.Kind() == reflect.Float64 ||
itemNode.Kind() == reflect.Complex64 || itemNode.Kind() == reflect.Complex128 {
continue
}
k := itemNode.Kind()
_ = k
if itemNode.Kind() == reflect.Array || itemNode.Kind() == reflect.Slice {
itemNodePtr := itemNode.Addr()
if _, ok := visited[itemNodePtr]; ok {
continue
}
visited[itemNodePtr] = true
//log.Printf("%v", itemNodePtr)
} else {
itemNodePtr := itemNode.Addr()
if _, ok := visited[itemNodePtr]; ok {
continue
}
visited[itemNodePtr] = true
//log.Printf("%v", itemNodePtr)
}
queue = append(queue, itemNode)
}
}
}
这是一个使用反射和模板的解决方案,用于查找变量。函数parseTemplateVariables
接受一个模板字符串作为参数。它首先解析模板,并将解析结果存储在parsedTemplate
中。然后,它使用广度优先搜索算法遍历解析结果的节点。
在遍历过程中,它会检查节点的类型。如果节点是parse.NodeField
类型,表示它是一个变量节点,会打印出相应的信息。对于其他类型的节点,会继续遍历其子节点。
该函数使用了反射来处理不同类型的节点。它会判断节点的类型,并根据类型进行相应的处理。如果节点是数组或切片类型,会将其指针添加到visited
映射中,以避免重复处理。如果节点是其他类型,也会将其指针添加到visited
映射中,并将其加入遍历队列中。
这个解决方案可以帮助你找到模板中的变量,并进行相应的处理。
英文:
solution for find variables, with reflect + template
func parseTemplateVariables(template string) {
parsedTemplate, err := text_template.New("test").Option("missingkey=error").Parse(template)
if err != nil {
panic(err)
}
visited := make(map[interface{}]bool)
queue := []reflect.Value{reflect.ValueOf(parsedTemplate.Root)}
for len(queue) != 0 {
node := queue[0]
queue = queue[1:]
for node.Kind() != reflect.Struct && node.Kind() != reflect.Array && node.Kind() != reflect.Slice {
node = node.Elem()
}
if node.CanInterface() {
vInterface := node.Interface()
if listNode, ok := vInterface.([]parse.Node); ok {
for _, itemNode := range listNode {
if itemNode.Type() == parse.NodeField {
log.Printf("using variable: %v", itemNode)
}
}
}
}
var n int
if node.Kind() == reflect.Array || node.Kind() == reflect.Slice {
n = node.Len()
} else {
n = node.NumField()
}
for i := 0; i != n; i++ {
var itemNode reflect.Value
if node.Kind() == reflect.Array || node.Kind() == reflect.Slice {
itemNode = node.Index(i)
} else {
itemNode = node.Field(i)
}
if !itemNode.IsValid() || itemNode.IsZero() {
continue
}
if itemNode.Kind() == reflect.Bool ||
itemNode.Kind() == reflect.String ||
itemNode.Kind() == reflect.Int || itemNode.Kind() == reflect.Int8 || itemNode.Kind() == reflect.Int16 || itemNode.Kind() == reflect.Int32 || itemNode.Kind() == reflect.Int64 ||
itemNode.Kind() == reflect.Uint || itemNode.Kind() == reflect.Uint8 || itemNode.Kind() == reflect.Uint16 || itemNode.Kind() == reflect.Uint32 || itemNode.Kind() == reflect.Uint64 || itemNode.Kind() == reflect.Uintptr ||
itemNode.Kind() == reflect.Float32 || itemNode.Kind() == reflect.Float64 ||
itemNode.Kind() == reflect.Complex64 || itemNode.Kind() == reflect.Complex128 {
continue
}
k := itemNode.Kind()
_ = k
if itemNode.Kind() == reflect.Array || itemNode.Kind() == reflect.Slice {
itemNodePtr := itemNode.Addr()
if _, ok := visited[itemNodePtr]; ok {
continue
}
visited[itemNodePtr] = true
//log.Printf("%v", itemNodePtr)
} else {
itemNodePtr := itemNode.Addr()
if _, ok := visited[itemNodePtr]; ok {
continue
}
visited[itemNodePtr] = true
//log.Printf("%v", itemNodePtr)
}
queue = append(queue, itemNode)
}
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论