1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
|
package pongo2
import (
"bytes"
"fmt"
"io"
"strings"
)
type TemplateWriter interface {
io.Writer
WriteString(string) (int, error)
}
type templateWriter struct {
w io.Writer
}
func (tw *templateWriter) WriteString(s string) (int, error) {
return tw.w.Write([]byte(s))
}
func (tw *templateWriter) Write(b []byte) (int, error) {
return tw.w.Write(b)
}
type Template struct {
set *TemplateSet
// Input
isTplString bool
name string
tpl string
size int
// Calculation
tokens []*Token
parser *Parser
// first come, first serve (it's important to not override existing entries in here)
level int
parent *Template
child *Template
blocks map[string]*NodeWrapper
exportedMacros map[string]*tagMacroNode
// Output
root *nodeDocument
// Options allow you to change the behavior of template-engine.
// You can change the options before calling the Execute method.
Options *Options
}
func newTemplateString(set *TemplateSet, tpl []byte) (*Template, error) {
return newTemplate(set, "<string>", true, tpl)
}
func newTemplate(set *TemplateSet, name string, isTplString bool, tpl []byte) (*Template, error) {
strTpl := string(tpl)
// Create the template
t := &Template{
set: set,
isTplString: isTplString,
name: name,
tpl: strTpl,
size: len(strTpl),
blocks: make(map[string]*NodeWrapper),
exportedMacros: make(map[string]*tagMacroNode),
Options: newOptions(),
}
// Copy all settings from another Options.
t.Options.Update(set.Options)
// Tokenize it
tokens, err := lex(name, strTpl)
if err != nil {
return nil, err
}
t.tokens = tokens
// For debugging purposes, show all tokens:
/*for i, t := range tokens {
fmt.Printf("%3d. %s\n", i, t)
}*/
// Parse it
err = t.parse()
if err != nil {
return nil, err
}
return t, nil
}
func (tpl *Template) newContextForExecution(context Context) (*Template, *ExecutionContext, error) {
if tpl.Options.TrimBlocks || tpl.Options.LStripBlocks {
// Issue #94 https://github.com/flosch/pongo2/issues/94
// If an application configures pongo2 template to trim_blocks,
// the first newline after a template tag is removed automatically (like in PHP).
prev := &Token{
Typ: TokenHTML,
Val: "\n",
}
for _, t := range tpl.tokens {
if tpl.Options.LStripBlocks {
if prev.Typ == TokenHTML && t.Typ != TokenHTML && t.Val == "{%" {
prev.Val = strings.TrimRight(prev.Val, "\t ")
}
}
if tpl.Options.TrimBlocks {
if prev.Typ != TokenHTML && t.Typ == TokenHTML && prev.Val == "%}" {
if len(t.Val) > 0 && t.Val[0] == '\n' {
t.Val = t.Val[1:len(t.Val)]
}
}
}
prev = t
}
}
// Determine the parent to be executed (for template inheritance)
parent := tpl
for parent.parent != nil {
parent = parent.parent
}
// Create context if none is given
newContext := make(Context)
newContext.Update(tpl.set.Globals)
if context != nil {
newContext.Update(context)
if len(newContext) > 0 {
// Check for context name syntax
err := newContext.checkForValidIdentifiers()
if err != nil {
return parent, nil, err
}
// Check for clashes with macro names
for k := range newContext {
_, has := tpl.exportedMacros[k]
if has {
return parent, nil, &Error{
Filename: tpl.name,
Sender: "execution",
OrigError: fmt.Errorf("context key name '%s' clashes with macro '%s'", k, k),
}
}
}
}
}
// Create operational context
ctx := newExecutionContext(parent, newContext)
return parent, ctx, nil
}
func (tpl *Template) execute(context Context, writer TemplateWriter) error {
parent, ctx, err := tpl.newContextForExecution(context)
if err != nil {
return err
}
// Run the selected document
if err := parent.root.Execute(ctx, writer); err != nil {
return err
}
return nil
}
func (tpl *Template) newTemplateWriterAndExecute(context Context, writer io.Writer) error {
return tpl.execute(context, &templateWriter{w: writer})
}
func (tpl *Template) newBufferAndExecute(context Context) (*bytes.Buffer, error) {
// Create output buffer
// We assume that the rendered template will be 30% larger
buffer := bytes.NewBuffer(make([]byte, 0, int(float64(tpl.size)*1.3)))
if err := tpl.execute(context, buffer); err != nil {
return nil, err
}
return buffer, nil
}
// Executes the template with the given context and writes to writer (io.Writer)
// on success. Context can be nil. Nothing is written on error; instead the error
// is being returned.
func (tpl *Template) ExecuteWriter(context Context, writer io.Writer) error {
buf, err := tpl.newBufferAndExecute(context)
if err != nil {
return err
}
_, err = buf.WriteTo(writer)
if err != nil {
return err
}
return nil
}
// Same as ExecuteWriter. The only difference between both functions is that
// this function might already have written parts of the generated template in the
// case of an execution error because there's no intermediate buffer involved for
// performance reasons. This is handy if you need high performance template
// generation or if you want to manage your own pool of buffers.
func (tpl *Template) ExecuteWriterUnbuffered(context Context, writer io.Writer) error {
return tpl.newTemplateWriterAndExecute(context, writer)
}
// Executes the template and returns the rendered template as a []byte
func (tpl *Template) ExecuteBytes(context Context) ([]byte, error) {
// Execute template
buffer, err := tpl.newBufferAndExecute(context)
if err != nil {
return nil, err
}
return buffer.Bytes(), nil
}
// Executes the template and returns the rendered template as a string
func (tpl *Template) Execute(context Context) (string, error) {
// Execute template
buffer, err := tpl.newBufferAndExecute(context)
if err != nil {
return "", err
}
return buffer.String(), nil
}
func (tpl *Template) ExecuteBlocks(context Context, blocks []string) (map[string]string, error) {
var parents []*Template
result := make(map[string]string)
parent := tpl
for parent != nil {
parents = append(parents, parent)
parent = parent.parent
}
for _, t := range parents {
buffer := bytes.NewBuffer(make([]byte, 0, int(float64(t.size)*1.3)))
_, ctx, err := t.newContextForExecution(context)
if err != nil {
return nil, err
}
for _, blockName := range blocks {
if _, ok := result[blockName]; ok {
continue
}
if blockWrapper, ok := t.blocks[blockName]; ok {
bErr := blockWrapper.Execute(ctx, buffer)
if bErr != nil {
return nil, bErr
}
result[blockName] = buffer.String()
buffer.Reset()
}
}
// We have found all blocks
if len(blocks) == len(result) {
break
}
}
return result, nil
}
|