1
0
mirror of https://github.com/golang/go synced 2024-09-24 23:20:12 -06: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:
Mike Samuel 2011-08-12 14:34:29 +10:00 committed by Rob Pike
parent 1b13131c6b
commit 595e9d5034
7 changed files with 200 additions and 5 deletions

View File

@ -83,6 +83,7 @@ DIRS=\
exp/norm\
exp/regexp/syntax\
exp/template\
exp/template/html\
exp/template/parse\
expvar\
flag\

View 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

View 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}

View 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)
}
}

View File

@ -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.

View File

@ -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}
}

View File

@ -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: