mirror of
https://github.com/golang/go
synced 2024-11-25 01:37:57 -07:00
exp/template/html: New package with a toy template transformation.
func Reverse(*Template) *Template returns a template that produces the reverse of the original for any input. Changes outside exp/template/html include: - Adding a getter for a template's FuncMap so that derived templates can inherit function definitions. - Exported one node factory function, newIdentifier. Deriving tempaltes requires constructing new nodes, but I didn't export all of them because I think shallow copy functions might be more useful for this kind of work. - Bugfix: Template's Name() method ignores the name field so template.New("foo") is a nil dereference instead of "foo". Caveats: Reverse is a toy. It is not UTF-8 safe, and does not preserve order of calls to funcs in FuncMap. For context, see http://groups.google.com/group/golang-nuts/browse_thread/thread/e8bc7c771aae3f20/b1ac41dc6f609b6e?lnk=gst R=rsc, r, nigeltao, r CC=golang-dev https://golang.org/cl/4808089
This commit is contained in:
parent
1b13131c6b
commit
595e9d5034
@ -83,6 +83,7 @@ DIRS=\
|
||||
exp/norm\
|
||||
exp/regexp/syntax\
|
||||
exp/template\
|
||||
exp/template/html\
|
||||
exp/template/parse\
|
||||
expvar\
|
||||
flag\
|
||||
|
11
src/pkg/exp/template/html/Makefile
Normal file
11
src/pkg/exp/template/html/Makefile
Normal file
@ -0,0 +1,11 @@
|
||||
# Copyright 2011 The Go Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
include ../../../../Make.inc
|
||||
|
||||
TARG=exp/template/html
|
||||
GOFILES=\
|
||||
reverse.go
|
||||
|
||||
include ../../../../Make.pkg
|
134
src/pkg/exp/template/html/reverse.go
Normal file
134
src/pkg/exp/template/html/reverse.go
Normal file
@ -0,0 +1,134 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package html is a specialization of exp/template that automates the
|
||||
// construction of safe HTML output.
|
||||
// At the moment it is just skeleton code that demonstrates how to derive
|
||||
// templates via AST -> AST transformations.
|
||||
package html
|
||||
|
||||
import (
|
||||
"exp/template"
|
||||
"exp/template/parse"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Reverse reverses a template.
|
||||
// After Reverse(t), t.Execute(wr, data) writes to wr the byte-wise reverse of
|
||||
// what would have been written otherwise.
|
||||
//
|
||||
// E.g.
|
||||
// Reverse(template.Parse("{{if .Coming}}Hello{{else}}Bye{{end}}, {{.World}}")
|
||||
// behaves like
|
||||
// template.Parse("{{.World | reverse}} ,{{if .Coming}}olleH{{else}}eyB{{end}}")
|
||||
func Reverse(t *template.Template) {
|
||||
t.Funcs(supportFuncs)
|
||||
|
||||
// If the parser shares trees based on common-subexpression
|
||||
// joining then we will need to avoid multiply reversing the same tree.
|
||||
reverseListNode(t.Tree.Root)
|
||||
}
|
||||
|
||||
// reverseNode dispatches to reverse<NodeType> helpers by type.
|
||||
func reverseNode(node parse.Node) {
|
||||
switch n := node.(type) {
|
||||
case *parse.ListNode:
|
||||
reverseListNode(n)
|
||||
case *parse.TextNode:
|
||||
reverseTextNode(n)
|
||||
case *parse.ActionNode:
|
||||
reverseActionNode(n)
|
||||
case *parse.IfNode:
|
||||
reverseIfNode(n)
|
||||
default:
|
||||
panic("handling for " + node.String() + " not implemented")
|
||||
// TODO: Handle other inner node types.
|
||||
}
|
||||
}
|
||||
|
||||
// reverseListNode recursively reverses its input's children and reverses their
|
||||
// order.
|
||||
func reverseListNode(node *parse.ListNode) {
|
||||
if node == nil {
|
||||
return
|
||||
}
|
||||
children := node.Nodes
|
||||
for _, child := range children {
|
||||
reverseNode(child)
|
||||
}
|
||||
for i, j := 0, len(children)-1; i < j; i, j = i+1, j-1 {
|
||||
children[i], children[j] = children[j], children[i]
|
||||
}
|
||||
}
|
||||
|
||||
// reverseTextNode reverses the text UTF-8 sequence by UTF-8 sequence.
|
||||
func reverseTextNode(node *parse.TextNode) {
|
||||
runes := []int(string(node.Text))
|
||||
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
|
||||
runes[i], runes[j] = runes[j], runes[i]
|
||||
}
|
||||
node.Text = []byte(string(runes))
|
||||
}
|
||||
|
||||
// reverseActionNode adds a pipeline call to the end that reverses the result
|
||||
// of the expression before it is interpolated into the template output.
|
||||
func reverseActionNode(node *parse.ActionNode) {
|
||||
pipe := node.Pipe
|
||||
|
||||
cmds := pipe.Cmds
|
||||
nCmds := len(cmds)
|
||||
|
||||
// If it's already been reversed, just slice out the reverse command.
|
||||
// This makes (Reverse o Reverse) almost the identity function
|
||||
// modulo changes to the templates FuncMap.
|
||||
if nCmds != 0 {
|
||||
if lastCmd := cmds[nCmds-1]; len(lastCmd.Args) != 0 {
|
||||
if arg, ok := lastCmd.Args[0].(*parse.IdentifierNode); ok && arg.Ident == "reverse" {
|
||||
pipe.Cmds = pipe.Cmds[:nCmds-1]
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reverseCommand := parse.CommandNode{
|
||||
NodeType: parse.NodeCommand,
|
||||
Args: []parse.Node{parse.NewIdentifier("reverse")},
|
||||
}
|
||||
|
||||
node.Pipe.Cmds = append(node.Pipe.Cmds, &reverseCommand)
|
||||
}
|
||||
|
||||
// reverseIfNode recursively reverses the if and then clauses but leaves the
|
||||
// condition unchanged.
|
||||
func reverseIfNode(node *parse.IfNode) {
|
||||
reverseListNode(node.List)
|
||||
reverseListNode(node.ElseList)
|
||||
}
|
||||
|
||||
// reverse writes the reverse of the given byte buffer to the given Writer.
|
||||
func reverse(x interface{}) string {
|
||||
var s string
|
||||
switch y := x.(type) {
|
||||
case nil:
|
||||
s = "<nil>"
|
||||
case []byte:
|
||||
// TODO: unnecessary buffer copy.
|
||||
s = string(y)
|
||||
case string:
|
||||
s = y
|
||||
case fmt.Stringer:
|
||||
s = y.String()
|
||||
default:
|
||||
s = fmt.Sprintf("<inconvertible of type %T>", x)
|
||||
}
|
||||
n := len(s)
|
||||
bytes := make([]byte, n)
|
||||
for i := 0; i < n; i++ {
|
||||
bytes[n-i-1] = s[i]
|
||||
}
|
||||
return string(bytes)
|
||||
}
|
||||
|
||||
// supportFuncs contains functions required by reversed template nodes.
|
||||
var supportFuncs = template.FuncMap{"reverse": reverse}
|
48
src/pkg/exp/template/html/reverse_test.go
Normal file
48
src/pkg/exp/template/html/reverse_test.go
Normal file
@ -0,0 +1,48 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package html
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"exp/template"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type data struct {
|
||||
World string
|
||||
Coming bool
|
||||
}
|
||||
|
||||
func TestReverse(t *testing.T) {
|
||||
templateSource :=
|
||||
"{{if .Coming}}Hello{{else}}Goodbye{{end}}, {{.World}}!"
|
||||
templateData := data{
|
||||
World: "Cincinatti",
|
||||
Coming: true,
|
||||
}
|
||||
|
||||
tmpl := template.New("test")
|
||||
tmpl, err := tmpl.Parse(templateSource)
|
||||
if err != nil {
|
||||
t.Errorf("failed to parse template: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
Reverse(tmpl)
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
|
||||
err = tmpl.Execute(buffer, templateData)
|
||||
if err != nil {
|
||||
t.Errorf("failed to execute reversed template: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
golden := "!ittanicniC ,olleH"
|
||||
actual := buffer.String()
|
||||
if golden != actual {
|
||||
t.Errorf("reversed output: %q != %q", golden, actual)
|
||||
}
|
||||
}
|
@ -24,7 +24,7 @@ type Template struct {
|
||||
|
||||
// Name returns the name of the template.
|
||||
func (t *Template) Name() string {
|
||||
return t.Tree.Name
|
||||
return t.name
|
||||
}
|
||||
|
||||
// Parsing.
|
||||
|
@ -31,14 +31,14 @@ func (t NodeType) Type() NodeType {
|
||||
|
||||
const (
|
||||
NodeText NodeType = iota // Plain text.
|
||||
NodeAction // An simple action such as field evaluation.
|
||||
NodeAction // A simple action such as field evaluation.
|
||||
NodeBool // A boolean constant.
|
||||
NodeCommand // An element of a pipeline.
|
||||
NodeDot // The cursor, dot.
|
||||
NodeElse // An else action.
|
||||
NodeEnd // An end action.
|
||||
NodeField // A field or method name.
|
||||
NodeIdentifier // A identifier; always a function name.
|
||||
NodeIdentifier // An identifier; always a function name.
|
||||
NodeIf // An if action.
|
||||
NodeList // A list of Nodes.
|
||||
NodeNumber // A numerical constant.
|
||||
@ -154,7 +154,8 @@ type IdentifierNode struct {
|
||||
Ident string // The identifier's name.
|
||||
}
|
||||
|
||||
func newIdentifier(ident string) *IdentifierNode {
|
||||
// NewIdentifier returns a new IdentifierNode with the given identifier name.
|
||||
func NewIdentifier(ident string) *IdentifierNode {
|
||||
return &IdentifierNode{NodeType: NodeIdentifier, Ident: ident}
|
||||
}
|
||||
|
||||
|
@ -373,7 +373,7 @@ Loop:
|
||||
if !t.hasFunction(token.val) {
|
||||
t.errorf("function %q not defined", token.val)
|
||||
}
|
||||
cmd.append(newIdentifier(token.val))
|
||||
cmd.append(NewIdentifier(token.val))
|
||||
case itemDot:
|
||||
cmd.append(newDot())
|
||||
case itemVariable:
|
||||
|
Loading…
Reference in New Issue
Block a user