Commit | Line | Data |
---|---|---|
c638d827 CR |
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" |