Premiere version : mise en route du suivi.
[auf_roundup.git] / build / lib / roundup / cgi / TAL / TALInterpreter.py
1 ##############################################################################
2 #
3 # Copyright (c) 2001, 2002 Zope Corporation and Contributors.
4 # All Rights Reserved.
5 #
6 # This software is subject to the provisions of the Zope Public License,
7 # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
8 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
9 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
10 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
11 # FOR A PARTICULAR PURPOSE.
12 #
13 ##############################################################################
14 # Modifications for Roundup:
15 # 1. implemented ustr as str
16 """
17 Interpreter for a pre-compiled TAL program.
18 """
19
20 import sys
21 import getopt
22 import re
23 from types import ListType
24 from cgi import escape
25 # Do not use cStringIO here! It's not unicode aware. :(
26 from StringIO import StringIO
27 #from DocumentTemplate.DT_Util import ustr
28 ustr = str
29
30 from TALDefs import TAL_VERSION, TALError, METALError, attrEscape
31 from TALDefs import isCurrentVersion, getProgramVersion, getProgramMode
32 from TALGenerator import TALGenerator
33 from TranslationContext import TranslationContext
34
35 BOOLEAN_HTML_ATTRS = [
36 # List of Boolean attributes in HTML that should be rendered in
37 # minimized form (e.g. <img ismap> rather than <img ismap="">)
38 # From http://www.w3.org/TR/xhtml1/#guidelines (C.10)
39 # XXX The problem with this is that this is not valid XML and
40 # can't be parsed back!
41 "compact", "nowrap", "ismap", "declare", "noshade", "checked",
42 "disabled", "readonly", "multiple", "selected", "noresize",
43 "defer"
44 ]
45
46 def normalize(text):
47 # Now we need to normalize the whitespace in implicit message ids and
48 # implicit $name substitution values by stripping leading and trailing
49 # whitespace, and folding all internal whitespace to a single space.
50 return ' '.join(text.split())
51
52
53 NAME_RE = r"[a-zA-Z][a-zA-Z0-9_]*"
54 _interp_regex = re.compile(r'(?<!\$)(\$(?:%(n)s|{%(n)s}))' %({'n': NAME_RE}))
55 _get_var_regex = re.compile(r'%(n)s' %({'n': NAME_RE}))
56
57 def interpolate(text, mapping):
58 """Interpolate ${keyword} substitutions.
59
60 This is called when no translation is provided by the translation
61 service.
62 """
63 if not mapping:
64 return text
65 # Find all the spots we want to substitute.
66 to_replace = _interp_regex.findall(text)
67 # Now substitute with the variables in mapping.
68 for string in to_replace:
69 var = _get_var_regex.findall(string)[0]
70 if mapping.has_key(var):
71 # Call ustr because we may have an integer for instance.
72 subst = ustr(mapping[var])
73 try:
74 text = text.replace(string, subst)
75 except UnicodeError:
76 # subst contains high-bit chars...
77 # As we have no way of knowing the correct encoding,
78 # substitue something instead of raising an exception.
79 subst = `subst`[1:-1]
80 text = text.replace(string, subst)
81 return text
82
83
84 class AltTALGenerator(TALGenerator):
85
86 def __init__(self, repldict, expressionCompiler=None, xml=0):
87 self.repldict = repldict
88 self.enabled = 1
89 TALGenerator.__init__(self, expressionCompiler, xml)
90
91 def enable(self, enabled):
92 self.enabled = enabled
93
94 def emit(self, *args):
95 if self.enabled:
96 TALGenerator.emit(self, *args)
97
98 def emitStartElement(self, name, attrlist, taldict, metaldict, i18ndict,
99 position=(None, None), isend=0):
100 metaldict = {}
101 taldict = {}
102 i18ndict = {}
103 if self.enabled and self.repldict:
104 taldict["attributes"] = "x x"
105 TALGenerator.emitStartElement(self, name, attrlist,
106 taldict, metaldict, i18ndict,
107 position, isend)
108
109 def replaceAttrs(self, attrlist, repldict):
110 if self.enabled and self.repldict:
111 repldict = self.repldict
112 self.repldict = None
113 return TALGenerator.replaceAttrs(self, attrlist, repldict)
114
115
116 class TALInterpreter:
117
118 def __init__(self, program, macros, engine, stream=None,
119 debug=0, wrap=60, metal=1, tal=1, showtal=-1,
120 strictinsert=1, stackLimit=100, i18nInterpolate=1):
121 self.program = program
122 self.macros = macros
123 self.engine = engine # Execution engine (aka context)
124 self.Default = engine.getDefault()
125 self.stream = stream or sys.stdout
126 self._stream_write = self.stream.write
127 self.debug = debug
128 self.wrap = wrap
129 self.metal = metal
130 self.tal = tal
131 if tal:
132 self.dispatch = self.bytecode_handlers_tal
133 else:
134 self.dispatch = self.bytecode_handlers
135 assert showtal in (-1, 0, 1)
136 if showtal == -1:
137 showtal = (not tal)
138 self.showtal = showtal
139 self.strictinsert = strictinsert
140 self.stackLimit = stackLimit
141 self.html = 0
142 self.endsep = "/>"
143 self.endlen = len(self.endsep)
144 self.macroStack = []
145 self.position = None, None # (lineno, offset)
146 self.col = 0
147 self.level = 0
148 self.scopeLevel = 0
149 self.sourceFile = None
150 self.i18nStack = []
151 self.i18nInterpolate = i18nInterpolate
152 self.i18nContext = TranslationContext()
153
154 def StringIO(self):
155 # Third-party products wishing to provide a full Unicode-aware
156 # StringIO can do so by monkey-patching this method.
157 return FasterStringIO()
158
159 def saveState(self):
160 return (self.position, self.col, self.stream,
161 self.scopeLevel, self.level, self.i18nContext)
162
163 def restoreState(self, state):
164 (self.position, self.col, self.stream, scopeLevel, level, i18n) = state
165 self._stream_write = self.stream.write
166 assert self.level == level
167 while self.scopeLevel > scopeLevel:
168 self.engine.endScope()
169 self.scopeLevel = self.scopeLevel - 1
170 self.engine.setPosition(self.position)
171 self.i18nContext = i18n
172
173 def restoreOutputState(self, state):
174 (dummy, self.col, self.stream, scopeLevel, level, i18n) = state
175 self._stream_write = self.stream.write
176 assert self.level == level
177 assert self.scopeLevel == scopeLevel
178
179 def pushMacro(self, macroName, slots, entering=1):
180 if len(self.macroStack) >= self.stackLimit:
181 raise METALError("macro nesting limit (%d) exceeded "
182 "by %s" % (self.stackLimit, `macroName`))
183 self.macroStack.append([macroName, slots, entering, self.i18nContext])
184
185 def popMacro(self):
186 return self.macroStack.pop()
187
188 def __call__(self):
189 assert self.level == 0
190 assert self.scopeLevel == 0
191 assert self.i18nContext.parent is None
192 self.interpret(self.program)
193 assert self.level == 0
194 assert self.scopeLevel == 0
195 assert self.i18nContext.parent is None
196 if self.col > 0:
197 self._stream_write("\n")
198 self.col = 0
199
200 def stream_write(self, s,
201 len=len):
202 self._stream_write(s)
203 i = s.rfind('\n')
204 if i < 0:
205 self.col = self.col + len(s)
206 else:
207 self.col = len(s) - (i + 1)
208
209 bytecode_handlers = {}
210
211 def interpretWithStream(self, program, stream):
212 oldstream = self.stream
213 self.stream = stream
214 self._stream_write = stream.write
215 try:
216 self.interpret(program)
217 finally:
218 self.stream = oldstream
219 self._stream_write = oldstream.write
220
221 def interpret(self, program):
222 oldlevel = self.level
223 self.level = oldlevel + 1
224 handlers = self.dispatch
225 try:
226 if self.debug:
227 for (opcode, args) in program:
228 s = "%sdo_%s(%s)\n" % (" "*self.level, opcode,
229 repr(args))
230 if len(s) > 80:
231 s = s[:76] + "...\n"
232 sys.stderr.write(s)
233 handlers[opcode](self, args)
234 else:
235 for (opcode, args) in program:
236 handlers[opcode](self, args)
237 finally:
238 self.level = oldlevel
239
240 def do_version(self, version):
241 assert version == TAL_VERSION
242 bytecode_handlers["version"] = do_version
243
244 def do_mode(self, mode):
245 assert mode in ("html", "xml")
246 self.html = (mode == "html")
247 if self.html:
248 self.endsep = " />"
249 else:
250 self.endsep = "/>"
251 self.endlen = len(self.endsep)
252 bytecode_handlers["mode"] = do_mode
253
254 def do_setSourceFile(self, source_file):
255 self.sourceFile = source_file
256 self.engine.setSourceFile(source_file)
257 bytecode_handlers["setSourceFile"] = do_setSourceFile
258
259 def do_setPosition(self, position):
260 self.position = position
261 self.engine.setPosition(position)
262 bytecode_handlers["setPosition"] = do_setPosition
263
264 def do_startEndTag(self, stuff):
265 self.do_startTag(stuff, self.endsep, self.endlen)
266 bytecode_handlers["startEndTag"] = do_startEndTag
267
268 def do_startTag(self, (name, attrList),
269 end=">", endlen=1, _len=len):
270 # The bytecode generator does not cause calls to this method
271 # for start tags with no attributes; those are optimized down
272 # to rawtext events. Hence, there is no special "fast path"
273 # for that case.
274 L = ["<", name]
275 append = L.append
276 col = self.col + _len(name) + 1
277 wrap = self.wrap
278 align = col + 1
279 if align >= wrap/2:
280 align = 4 # Avoid a narrow column far to the right
281 attrAction = self.dispatch["<attrAction>"]
282 try:
283 for item in attrList:
284 if _len(item) == 2:
285 name, s = item
286 else:
287 # item[2] is the 'action' field:
288 if item[2] in ('metal', 'tal', 'xmlns', 'i18n'):
289 if not self.showtal:
290 continue
291 ok, name, s = self.attrAction(item)
292 else:
293 ok, name, s = attrAction(self, item)
294 if not ok:
295 continue
296 slen = _len(s)
297 if (wrap and
298 col >= align and
299 col + 1 + slen > wrap):
300 append("\n")
301 append(" "*align)
302 col = align + slen
303 else:
304 append(" ")
305 col = col + 1 + slen
306 append(s)
307 append(end)
308 self._stream_write("".join(L))
309 col = col + endlen
310 finally:
311 self.col = col
312 bytecode_handlers["startTag"] = do_startTag
313
314 def attrAction(self, item):
315 name, value, action = item[:3]
316 if action == 'insert':
317 return 0, name, value
318 macs = self.macroStack
319 if action == 'metal' and self.metal and macs:
320 if len(macs) > 1 or not macs[-1][2]:
321 # Drop all METAL attributes at a use-depth above one.
322 return 0, name, value
323 # Clear 'entering' flag
324 macs[-1][2] = 0
325 # Convert or drop depth-one METAL attributes.
326 i = name.rfind(":") + 1
327 prefix, suffix = name[:i], name[i:]
328 if suffix == "define-macro":
329 # Convert define-macro as we enter depth one.
330 name = prefix + "use-macro"
331 value = macs[-1][0] # Macro name
332 elif suffix == "define-slot":
333 name = prefix + "fill-slot"
334 elif suffix == "fill-slot":
335 pass
336 else:
337 return 0, name, value
338
339 if value is None:
340 value = name
341 else:
342 value = '%s="%s"' % (name, attrEscape(value))
343 return 1, name, value
344
345 def attrAction_tal(self, item):
346 name, value, action = item[:3]
347 ok = 1
348 expr, xlat, msgid = item[3:]
349 if self.html and name.lower() in BOOLEAN_HTML_ATTRS:
350 evalue = self.engine.evaluateBoolean(item[3])
351 if evalue is self.Default:
352 if action == 'insert': # Cancelled insert
353 ok = 0
354 elif evalue:
355 value = None
356 else:
357 ok = 0
358 elif expr is not None:
359 evalue = self.engine.evaluateText(item[3])
360 if evalue is self.Default:
361 if action == 'insert': # Cancelled insert
362 ok = 0
363 else:
364 if evalue is None:
365 ok = 0
366 value = evalue
367 else:
368 evalue = None
369
370 if ok:
371 if xlat:
372 translated = self.translate(msgid or value, value, {})
373 if translated is not None:
374 value = translated
375 if value is None:
376 value = name
377 elif evalue is self.Default:
378 value = attrEscape(value)
379 else:
380 value = escape(value, quote=1)
381 value = '%s="%s"' % (name, value)
382 return ok, name, value
383 bytecode_handlers["<attrAction>"] = attrAction
384
385 def no_tag(self, start, program):
386 state = self.saveState()
387 self.stream = stream = self.StringIO()
388 self._stream_write = stream.write
389 self.interpret(start)
390 self.restoreOutputState(state)
391 self.interpret(program)
392
393 def do_optTag(self, (name, cexpr, tag_ns, isend, start, program),
394 omit=0):
395 if tag_ns and not self.showtal:
396 return self.no_tag(start, program)
397
398 self.interpret(start)
399 if not isend:
400 self.interpret(program)
401 s = '</%s>' % name
402 self._stream_write(s)
403 self.col = self.col + len(s)
404
405 def do_optTag_tal(self, stuff):
406 cexpr = stuff[1]
407 if cexpr is not None and (cexpr == '' or
408 self.engine.evaluateBoolean(cexpr)):
409 self.no_tag(stuff[-2], stuff[-1])
410 else:
411 self.do_optTag(stuff)
412 bytecode_handlers["optTag"] = do_optTag
413
414 def do_rawtextBeginScope(self, (s, col, position, closeprev, dict)):
415 self._stream_write(s)
416 self.col = col
417 self.position = position
418 self.engine.setPosition(position)
419 if closeprev:
420 engine = self.engine
421 engine.endScope()
422 engine.beginScope()
423 else:
424 self.engine.beginScope()
425 self.scopeLevel = self.scopeLevel + 1
426
427 def do_rawtextBeginScope_tal(self, (s, col, position, closeprev, dict)):
428 self._stream_write(s)
429 self.col = col
430 engine = self.engine
431 self.position = position
432 engine.setPosition(position)
433 if closeprev:
434 engine.endScope()
435 engine.beginScope()
436 else:
437 engine.beginScope()
438 self.scopeLevel = self.scopeLevel + 1
439 engine.setLocal("attrs", dict)
440 bytecode_handlers["rawtextBeginScope"] = do_rawtextBeginScope
441
442 def do_beginScope(self, dict):
443 self.engine.beginScope()
444 self.scopeLevel = self.scopeLevel + 1
445
446 def do_beginScope_tal(self, dict):
447 engine = self.engine
448 engine.beginScope()
449 engine.setLocal("attrs", dict)
450 self.scopeLevel = self.scopeLevel + 1
451 bytecode_handlers["beginScope"] = do_beginScope
452
453 def do_endScope(self, notused=None):
454 self.engine.endScope()
455 self.scopeLevel = self.scopeLevel - 1
456 bytecode_handlers["endScope"] = do_endScope
457
458 def do_setLocal(self, notused):
459 pass
460
461 def do_setLocal_tal(self, (name, expr)):
462 self.engine.setLocal(name, self.engine.evaluateValue(expr))
463 bytecode_handlers["setLocal"] = do_setLocal
464
465 def do_setGlobal_tal(self, (name, expr)):
466 self.engine.setGlobal(name, self.engine.evaluateValue(expr))
467 bytecode_handlers["setGlobal"] = do_setLocal
468
469 def do_beginI18nContext(self, settings):
470 get = settings.get
471 self.i18nContext = TranslationContext(self.i18nContext,
472 domain=get("domain"),
473 source=get("source"),
474 target=get("target"))
475 bytecode_handlers["beginI18nContext"] = do_beginI18nContext
476
477 def do_endI18nContext(self, notused=None):
478 self.i18nContext = self.i18nContext.parent
479 assert self.i18nContext is not None
480 bytecode_handlers["endI18nContext"] = do_endI18nContext
481
482 def do_insertText(self, stuff):
483 self.interpret(stuff[1])
484
485 def do_insertText_tal(self, stuff):
486 text = self.engine.evaluateText(stuff[0])
487 if text is None:
488 return
489 if text is self.Default:
490 self.interpret(stuff[1])
491 return
492 s = escape(text)
493 self._stream_write(s)
494 i = s.rfind('\n')
495 if i < 0:
496 self.col = self.col + len(s)
497 else:
498 self.col = len(s) - (i + 1)
499 bytecode_handlers["insertText"] = do_insertText
500
501 def do_i18nVariable(self, stuff):
502 varname, program, expression = stuff
503 if expression is None:
504 # The value is implicitly the contents of this tag, so we have to
505 # evaluate the mini-program to get the value of the variable.
506 state = self.saveState()
507 try:
508 tmpstream = self.StringIO()
509 self.interpretWithStream(program, tmpstream)
510 value = normalize(tmpstream.getvalue())
511 finally:
512 self.restoreState(state)
513 else:
514 # Evaluate the value to be associated with the variable in the
515 # i18n interpolation dictionary.
516 value = self.engine.evaluate(expression)
517 # Either the i18n:name tag is nested inside an i18n:translate in which
518 # case the last item on the stack has the i18n dictionary and string
519 # representation, or the i18n:name and i18n:translate attributes are
520 # in the same tag, in which case the i18nStack will be empty. In that
521 # case we can just output the ${name} to the stream
522 i18ndict, srepr = self.i18nStack[-1]
523 i18ndict[varname] = value
524 placeholder = '${%s}' % varname
525 srepr.append(placeholder)
526 self._stream_write(placeholder)
527 bytecode_handlers['i18nVariable'] = do_i18nVariable
528
529 def do_insertTranslation(self, stuff):
530 i18ndict = {}
531 srepr = []
532 obj = None
533 self.i18nStack.append((i18ndict, srepr))
534 msgid = stuff[0]
535 # We need to evaluate the content of the tag because that will give us
536 # several useful pieces of information. First, the contents will
537 # include an implicit message id, if no explicit one was given.
538 # Second, it will evaluate any i18nVariable definitions in the body of
539 # the translation (necessary for $varname substitutions).
540 #
541 # Use a temporary stream to capture the interpretation of the
542 # subnodes, which should /not/ go to the output stream.
543 tmpstream = self.StringIO()
544 self.interpretWithStream(stuff[1], tmpstream)
545 default = tmpstream.getvalue()
546 # We only care about the evaluated contents if we need an implicit
547 # message id. All other useful information will be in the i18ndict on
548 # the top of the i18nStack.
549 if msgid == '':
550 msgid = normalize(default)
551 self.i18nStack.pop()
552 # See if there is was an i18n:data for msgid
553 if len(stuff) > 2:
554 obj = self.engine.evaluate(stuff[2])
555 xlated_msgid = self.translate(msgid, default, i18ndict, obj)
556 assert xlated_msgid is not None, self.position
557 self._stream_write(xlated_msgid)
558 bytecode_handlers['insertTranslation'] = do_insertTranslation
559
560 def do_insertStructure(self, stuff):
561 self.interpret(stuff[2])
562
563 def do_insertStructure_tal(self, (expr, repldict, block)):
564 structure = self.engine.evaluateStructure(expr)
565 if structure is None:
566 return
567 if structure is self.Default:
568 self.interpret(block)
569 return
570 text = ustr(structure)
571 if not (repldict or self.strictinsert):
572 # Take a shortcut, no error checking
573 self.stream_write(text)
574 return
575 if self.html:
576 self.insertHTMLStructure(text, repldict)
577 else:
578 self.insertXMLStructure(text, repldict)
579 bytecode_handlers["insertStructure"] = do_insertStructure
580
581 def insertHTMLStructure(self, text, repldict):
582 from HTMLTALParser import HTMLTALParser
583 gen = AltTALGenerator(repldict, self.engine.getCompiler(), 0)
584 p = HTMLTALParser(gen) # Raises an exception if text is invalid
585 p.parseString(text)
586 program, macros = p.getCode()
587 self.interpret(program)
588
589 def insertXMLStructure(self, text, repldict):
590 from TALParser import TALParser
591 gen = AltTALGenerator(repldict, self.engine.getCompiler(), 0)
592 p = TALParser(gen)
593 gen.enable(0)
594 p.parseFragment('<!DOCTYPE foo PUBLIC "foo" "bar"><foo>')
595 gen.enable(1)
596 p.parseFragment(text) # Raises an exception if text is invalid
597 gen.enable(0)
598 p.parseFragment('</foo>', 1)
599 program, macros = gen.getCode()
600 self.interpret(program)
601
602 def do_loop(self, (name, expr, block)):
603 self.interpret(block)
604
605 def do_loop_tal(self, (name, expr, block)):
606 iterator = self.engine.setRepeat(name, expr)
607 while iterator.next():
608 self.interpret(block)
609 bytecode_handlers["loop"] = do_loop
610
611 def translate(self, msgid, default, i18ndict, obj=None):
612 if obj:
613 i18ndict.update(obj)
614 if not self.i18nInterpolate:
615 return msgid
616 # XXX We need to pass in one of context or target_language
617 return self.engine.translate(self.i18nContext.domain,
618 msgid, i18ndict, default=default)
619
620 def do_rawtextColumn(self, (s, col)):
621 self._stream_write(s)
622 self.col = col
623 bytecode_handlers["rawtextColumn"] = do_rawtextColumn
624
625 def do_rawtextOffset(self, (s, offset)):
626 self._stream_write(s)
627 self.col = self.col + offset
628 bytecode_handlers["rawtextOffset"] = do_rawtextOffset
629
630 def do_condition(self, (condition, block)):
631 if not self.tal or self.engine.evaluateBoolean(condition):
632 self.interpret(block)
633 bytecode_handlers["condition"] = do_condition
634
635 def do_defineMacro(self, (macroName, macro)):
636 macs = self.macroStack
637 if len(macs) == 1:
638 entering = macs[-1][2]
639 if not entering:
640 macs.append(None)
641 self.interpret(macro)
642 assert macs[-1] is None
643 macs.pop()
644 return
645 self.interpret(macro)
646 bytecode_handlers["defineMacro"] = do_defineMacro
647
648 def do_useMacro(self, (macroName, macroExpr, compiledSlots, block)):
649 if not self.metal:
650 self.interpret(block)
651 return
652 macro = self.engine.evaluateMacro(macroExpr)
653 if macro is self.Default:
654 macro = block
655 else:
656 if not isCurrentVersion(macro):
657 raise METALError("macro %s has incompatible version %s" %
658 (`macroName`, `getProgramVersion(macro)`),
659 self.position)
660 mode = getProgramMode(macro)
661 if mode != (self.html and "html" or "xml"):
662 raise METALError("macro %s has incompatible mode %s" %
663 (`macroName`, `mode`), self.position)
664 self.pushMacro(macroName, compiledSlots)
665 prev_source = self.sourceFile
666 self.interpret(macro)
667 if self.sourceFile != prev_source:
668 self.engine.setSourceFile(prev_source)
669 self.sourceFile = prev_source
670 self.popMacro()
671 bytecode_handlers["useMacro"] = do_useMacro
672
673 def do_fillSlot(self, (slotName, block)):
674 # This is only executed if the enclosing 'use-macro' evaluates
675 # to 'default'.
676 self.interpret(block)
677 bytecode_handlers["fillSlot"] = do_fillSlot
678
679 def do_defineSlot(self, (slotName, block)):
680 if not self.metal:
681 self.interpret(block)
682 return
683 macs = self.macroStack
684 if macs and macs[-1] is not None:
685 macroName, slots = self.popMacro()[:2]
686 slot = slots.get(slotName)
687 if slot is not None:
688 prev_source = self.sourceFile
689 self.interpret(slot)
690 if self.sourceFile != prev_source:
691 self.engine.setSourceFile(prev_source)
692 self.sourceFile = prev_source
693 self.pushMacro(macroName, slots, entering=0)
694 return
695 self.pushMacro(macroName, slots)
696 # Falling out of the 'if' allows the macro to be interpreted.
697 self.interpret(block)
698 bytecode_handlers["defineSlot"] = do_defineSlot
699
700 def do_onError(self, (block, handler)):
701 self.interpret(block)
702
703 def do_onError_tal(self, (block, handler)):
704 state = self.saveState()
705 self.stream = stream = self.StringIO()
706 self._stream_write = stream.write
707 try:
708 self.interpret(block)
709 except:
710 exc = sys.exc_info()[1]
711 self.restoreState(state)
712 engine = self.engine
713 engine.beginScope()
714 error = engine.createErrorInfo(exc, self.position)
715 engine.setLocal('error', error)
716 try:
717 self.interpret(handler)
718 finally:
719 engine.endScope()
720 else:
721 self.restoreOutputState(state)
722 self.stream_write(stream.getvalue())
723 bytecode_handlers["onError"] = do_onError
724
725 bytecode_handlers_tal = bytecode_handlers.copy()
726 bytecode_handlers_tal["rawtextBeginScope"] = do_rawtextBeginScope_tal
727 bytecode_handlers_tal["beginScope"] = do_beginScope_tal
728 bytecode_handlers_tal["setLocal"] = do_setLocal_tal
729 bytecode_handlers_tal["setGlobal"] = do_setGlobal_tal
730 bytecode_handlers_tal["insertStructure"] = do_insertStructure_tal
731 bytecode_handlers_tal["insertText"] = do_insertText_tal
732 bytecode_handlers_tal["loop"] = do_loop_tal
733 bytecode_handlers_tal["onError"] = do_onError_tal
734 bytecode_handlers_tal["<attrAction>"] = attrAction_tal
735 bytecode_handlers_tal["optTag"] = do_optTag_tal
736
737
738 class FasterStringIO(StringIO):
739 """Append-only version of StringIO.
740
741 This let's us have a much faster write() method.
742 """
743 def close(self):
744 if not self.closed:
745 self.write = _write_ValueError
746 StringIO.close(self)
747
748 def seek(self, pos, mode=0):
749 raise RuntimeError("FasterStringIO.seek() not allowed")
750
751 def write(self, s):
752 #assert self.pos == self.len
753 self.buflist.append(s)
754 self.len = self.pos = self.pos + len(s)
755
756
757 def _write_ValueError(s):
758 raise ValueError, "I/O operation on closed file"