comparison roundup/cgi/TAL/TALGenerator.py @ 2348:8c2402a78bb0

beginning getting ZPT up to date: TAL first
author Richard Jones <richard@users.sourceforge.net>
date Fri, 21 May 2004 05:36:30 +0000
parents fc52d57c6c3e
children 198b6e810c67
comparison
equal deleted inserted replaced
2347:fbbda3b1816d 2348:8c2402a78bb0
1 ############################################################################## 1 ##############################################################################
2 # 2 #
3 # Copyright (c) 2001, 2002 Zope Corporation and Contributors. 3 # Copyright (c) 2001, 2002 Zope Corporation and Contributors.
4 # All Rights Reserved. 4 # All Rights Reserved.
5 # 5 #
6 # This software is subject to the provisions of the Zope Public License, 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. 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 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 9 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
10 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 10 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
11 # FOR A PARTICULAR PURPOSE 11 # FOR A PARTICULAR PURPOSE.
12 # 12 #
13 ############################################################################## 13 ##############################################################################
14 """Code generator for TALInterpreter intermediate code.
15 """ 14 """
16 __docformat__ = 'restructuredtext' 15 Code generator for TALInterpreter intermediate code.
17 16 """
18 import string 17
19 import re 18 import re
20 import cgi 19 import cgi
21 20
22 from TALDefs import * 21 import TALDefs
22
23 from TALDefs import NAME_RE, TAL_VERSION
24 from TALDefs import I18NError, METALError, TALError
25 from TALDefs import parseSubstitution
26 from TranslationContext import TranslationContext, DEFAULT_DOMAIN
27
28 I18N_REPLACE = 1
29 I18N_CONTENT = 2
30 I18N_EXPRESSION = 3
31
32 _name_rx = re.compile(NAME_RE)
33
23 34
24 class TALGenerator: 35 class TALGenerator:
25 36
26 inMacroUse = 0 37 inMacroUse = 0
27 inMacroDef = 0 38 inMacroDef = 0
28 source_file = None 39 source_file = None
29 40
30 def __init__(self, expressionCompiler=None, xml=1, source_file=None): 41 def __init__(self, expressionCompiler=None, xml=1, source_file=None):
31 if not expressionCompiler: 42 if not expressionCompiler:
32 from DummyEngine import DummyEngine 43 from DummyEngine import DummyEngine
33 expressionCompiler = DummyEngine() 44 expressionCompiler = DummyEngine()
34 self.expressionCompiler = expressionCompiler 45 self.expressionCompiler = expressionCompiler
35 self.CompilerError = expressionCompiler.getCompilerError() 46 self.CompilerError = expressionCompiler.getCompilerError()
47 # This holds the emitted opcodes representing the input
36 self.program = [] 48 self.program = []
49 # The program stack for when we need to do some sub-evaluation for an
50 # intermediate result. E.g. in an i18n:name tag for which the
51 # contents describe the ${name} value.
37 self.stack = [] 52 self.stack = []
53 # Another stack of postponed actions. Elements on this stack are a
54 # dictionary; key/values contain useful information that
55 # emitEndElement needs to finish its calculations
38 self.todoStack = [] 56 self.todoStack = []
39 self.macros = {} 57 self.macros = {}
40 self.slots = {} 58 self.slots = {}
41 self.slotStack = [] 59 self.slotStack = []
42 self.xml = xml 60 self.xml = xml
43 self.emit("version", TAL_VERSION) 61 self.emit("version", TAL_VERSION)
44 self.emit("mode", xml and "xml" or "html") 62 self.emit("mode", xml and "xml" or "html")
45 if source_file is not None: 63 if source_file is not None:
46 self.source_file = source_file 64 self.source_file = source_file
47 self.emit("setSourceFile", source_file) 65 self.emit("setSourceFile", source_file)
66 self.i18nContext = TranslationContext()
67 self.i18nLevel = 0
48 68
49 def getCode(self): 69 def getCode(self):
50 assert not self.stack 70 assert not self.stack
51 assert not self.todoStack 71 assert not self.todoStack
52 return self.optimize(self.program), self.macros 72 return self.optimize(self.program), self.macros
53 73
54 def optimize(self, program): 74 def optimize(self, program):
55 output = [] 75 output = []
56 collect = [] 76 collect = []
57 rawseen = cursor = 0 77 cursor = 0
58 if self.xml: 78 if self.xml:
59 endsep = "/>" 79 endsep = "/>"
60 else: 80 else:
61 endsep = " />" 81 endsep = " />"
62 for cursor in xrange(len(program)+1): 82 for cursor in xrange(len(program)+1):
81 # Push *Scope instructions in front of any text instructions; 101 # Push *Scope instructions in front of any text instructions;
82 # this allows text instructions separated only by *Scope 102 # this allows text instructions separated only by *Scope
83 # instructions to be joined together. 103 # instructions to be joined together.
84 output.append(self.optimizeArgsList(item)) 104 output.append(self.optimizeArgsList(item))
85 continue 105 continue
86 text = string.join(collect, "") 106 if opcode == 'noop':
107 # This is a spacer for end tags in the face of i18n:name
108 # attributes. We can't let the optimizer collect immediately
109 # following end tags into the same rawtextOffset.
110 opcode = None
111 pass
112 text = "".join(collect)
87 if text: 113 if text:
88 i = string.rfind(text, "\n") 114 i = text.rfind("\n")
89 if i >= 0: 115 if i >= 0:
90 i = len(text) - (i + 1) 116 i = len(text) - (i + 1)
91 output.append(("rawtextColumn", (text, i))) 117 output.append(("rawtextColumn", (text, i)))
92 else: 118 else:
93 output.append(("rawtextOffset", (text, len(text)))) 119 output.append(("rawtextOffset", (text, len(text))))
94 if opcode != None: 120 if opcode != None:
95 output.append(self.optimizeArgsList(item)) 121 output.append(self.optimizeArgsList(item))
96 rawseen = cursor+1
97 collect = [] 122 collect = []
98 return self.optimizeCommonTriple(output) 123 return self.optimizeCommonTriple(output)
99 124
100 def optimizeArgsList(self, item): 125 def optimizeArgsList(self, item):
101 if len(item) == 2: 126 if len(item) == 2:
102 return item 127 return item
103 else: 128 else:
104 return item[0], tuple(item[1:]) 129 return item[0], tuple(item[1:])
105 130
106 actionIndex = {"replace":0, "insert":1, "metal":2, "tal":3, "xmlns":4, 131 # These codes are used to indicate what sort of special actions
107 0: 0, 1: 1, 2: 2, 3: 3, 4: 4} 132 # are needed for each special attribute. (Simple attributes don't
133 # get action codes.)
134 #
135 # The special actions (which are modal) are handled by
136 # TALInterpreter.attrAction() and .attrAction_tal().
137 #
138 # Each attribute is represented by a tuple:
139 #
140 # (name, value) -- a simple name/value pair, with
141 # no special processing
142 #
143 # (name, value, action, *extra) -- attribute with special
144 # processing needs, action is a
145 # code that indicates which
146 # branch to take, and *extra
147 # contains additional,
148 # action-specific information
149 # needed by the processing
150 #
108 def optimizeStartTag(self, collect, name, attrlist, end): 151 def optimizeStartTag(self, collect, name, attrlist, end):
152 # return true if the tag can be converted to plain text
109 if not attrlist: 153 if not attrlist:
110 collect.append("<%s%s" % (name, end)) 154 collect.append("<%s%s" % (name, end))
111 return 1 155 return 1
112 opt = 1 156 opt = 1
113 new = ["<" + name] 157 new = ["<" + name]
114 for i in range(len(attrlist)): 158 for i in range(len(attrlist)):
115 item = attrlist[i] 159 item = attrlist[i]
116 if len(item) > 2: 160 if len(item) > 2:
117 opt = 0 161 opt = 0
118 name, value, action = item[:3] 162 name, value, action = item[:3]
119 action = self.actionIndex[action]
120 attrlist[i] = (name, value, action) + item[3:] 163 attrlist[i] = (name, value, action) + item[3:]
121 else: 164 else:
122 if item[1] is None: 165 if item[1] is None:
123 s = item[0] 166 s = item[0]
124 else: 167 else:
125 s = "%s=%s" % (item[0], quote(item[1])) 168 s = '%s="%s"' % (item[0], TALDefs.attrEscape(item[1]))
126 attrlist[i] = item[0], s 169 attrlist[i] = item[0], s
127 if item[1] is None: 170 new.append(" " + s)
128 new.append(" " + item[0]) 171 # if no non-optimizable attributes were found, convert to plain text
129 else:
130 new.append(" %s=%s" % (item[0], quote(item[1])))
131 if opt: 172 if opt:
132 new.append(end) 173 new.append(end)
133 collect.extend(new) 174 collect.extend(new)
134 return opt 175 return opt
135 176
137 if len(program) < 3: 178 if len(program) < 3:
138 return program 179 return program
139 output = program[:2] 180 output = program[:2]
140 prev2, prev1 = output 181 prev2, prev1 = output
141 for item in program[2:]: 182 for item in program[2:]:
142 if ( item[0] == "beginScope" 183 if ( item[0] == "beginScope"
143 and prev1[0] == "setPosition" 184 and prev1[0] == "setPosition"
144 and prev2[0] == "rawtextColumn"): 185 and prev2[0] == "rawtextColumn"):
145 position = output.pop()[1] 186 position = output.pop()[1]
146 text, column = output.pop()[1] 187 text, column = output.pop()[1]
147 prev1 = None, None 188 prev1 = None, None
148 closeprev = 0 189 closeprev = 0
149 if output and output[-1][0] == "endScope": 190 if output and output[-1][0] == "endScope":
213 isend = 1 254 isend = 1
214 cexpr = optTag[0] 255 cexpr = optTag[0]
215 if cexpr: 256 if cexpr:
216 cexpr = self.compileExpression(optTag[0]) 257 cexpr = self.compileExpression(optTag[0])
217 self.emit("optTag", name, cexpr, optTag[1], isend, start, program) 258 self.emit("optTag", name, cexpr, optTag[1], isend, start, program)
218 259
219 def emitRawText(self, text): 260 def emitRawText(self, text):
220 self.emit("rawtext", text) 261 self.emit("rawtext", text)
221 262
222 def emitText(self, text): 263 def emitText(self, text):
223 self.emitRawText(cgi.escape(text)) 264 self.emitRawText(cgi.escape(text))
224 265
225 def emitDefines(self, defines): 266 def emitDefines(self, defines):
226 for part in splitParts(defines): 267 for part in TALDefs.splitParts(defines):
227 m = re.match( 268 m = re.match(
228 r"(?s)\s*(?:(global|local)\s+)?(%s)\s+(.*)\Z" % NAME_RE, part) 269 r"(?s)\s*(?:(global|local)\s+)?(%s)\s+(.*)\Z" % NAME_RE, part)
229 if not m: 270 if not m:
230 raise TALError("invalid define syntax: " + `part`, 271 raise TALError("invalid define syntax: " + `part`,
231 self.position) 272 self.position)
276 self.emit("insertText", cexpr, program) 317 self.emit("insertText", cexpr, program)
277 else: 318 else:
278 assert key == "structure" 319 assert key == "structure"
279 self.emit("insertStructure", cexpr, attrDict, program) 320 self.emit("insertStructure", cexpr, attrDict, program)
280 321
322 def emitI18nVariable(self, stuff):
323 # Used for i18n:name attributes. arg is extra information describing
324 # how the contents of the variable should get filled in, and it will
325 # either be a 1-tuple or a 2-tuple. If arg[0] is None, then the
326 # i18n:name value is taken implicitly from the contents of the tag,
327 # e.g. "I live in <span i18n:name="country">the USA</span>". In this
328 # case, arg[1] is the opcode sub-program describing the contents of
329 # the tag.
330 #
331 # When arg[0] is not None, it contains the tal expression used to
332 # calculate the contents of the variable, e.g.
333 # "I live in <span i18n:name="country"
334 # tal:replace="here/countryOfOrigin" />"
335 varname, action, expression = stuff
336 m = _name_rx.match(varname)
337 if m is None or m.group() != varname:
338 raise TALError("illegal i18n:name: %r" % varname, self.position)
339 key = cexpr = None
340 program = self.popProgram()
341 if action == I18N_REPLACE:
342 # This is a tag with an i18n:name and a tal:replace (implicit or
343 # explicit). Get rid of the first and last elements of the
344 # program, which are the start and end tag opcodes of the tag.
345 program = program[1:-1]
346 elif action == I18N_CONTENT:
347 # This is a tag with an i18n:name and a tal:content
348 # (explicit-only). Keep the first and last elements of the
349 # program, so we keep the start and end tag output.
350 pass
351 else:
352 assert action == I18N_EXPRESSION
353 key, expr = parseSubstitution(expression)
354 cexpr = self.compileExpression(expr)
355 # XXX Would key be anything but 'text' or None?
356 assert key in ('text', None)
357 self.emit('i18nVariable', varname, program, cexpr)
358
359 def emitTranslation(self, msgid, i18ndata):
360 program = self.popProgram()
361 if i18ndata is None:
362 self.emit('insertTranslation', msgid, program)
363 else:
364 key, expr = parseSubstitution(i18ndata)
365 cexpr = self.compileExpression(expr)
366 assert key == 'text'
367 self.emit('insertTranslation', msgid, program, cexpr)
368
281 def emitDefineMacro(self, macroName): 369 def emitDefineMacro(self, macroName):
282 program = self.popProgram() 370 program = self.popProgram()
283 macroName = string.strip(macroName) 371 macroName = macroName.strip()
284 if self.macros.has_key(macroName): 372 if self.macros.has_key(macroName):
285 raise METALError("duplicate macro definition: %s" % `macroName`, 373 raise METALError("duplicate macro definition: %s" % `macroName`,
286 self.position) 374 self.position)
287 if not re.match('%s$' % NAME_RE, macroName): 375 if not re.match('%s$' % NAME_RE, macroName):
288 raise METALError("invalid macro name: %s" % `macroName`, 376 raise METALError("invalid macro name: %s" % `macroName`,
297 self.inMacroUse = 0 385 self.inMacroUse = 0
298 self.emit("useMacro", expr, cexpr, self.popSlots(), program) 386 self.emit("useMacro", expr, cexpr, self.popSlots(), program)
299 387
300 def emitDefineSlot(self, slotName): 388 def emitDefineSlot(self, slotName):
301 program = self.popProgram() 389 program = self.popProgram()
302 slotName = string.strip(slotName) 390 slotName = slotName.strip()
303 if not re.match('%s$' % NAME_RE, slotName): 391 if not re.match('%s$' % NAME_RE, slotName):
304 raise METALError("invalid slot name: %s" % `slotName`, 392 raise METALError("invalid slot name: %s" % `slotName`,
305 self.position) 393 self.position)
306 self.emit("defineSlot", slotName, program) 394 self.emit("defineSlot", slotName, program)
307 395
308 def emitFillSlot(self, slotName): 396 def emitFillSlot(self, slotName):
309 program = self.popProgram() 397 program = self.popProgram()
310 slotName = string.strip(slotName) 398 slotName = slotName.strip()
311 if self.slots.has_key(slotName): 399 if self.slots.has_key(slotName):
312 raise METALError("duplicate fill-slot name: %s" % `slotName`, 400 raise METALError("duplicate fill-slot name: %s" % `slotName`,
313 self.position) 401 self.position)
314 if not re.match('%s$' % NAME_RE, slotName): 402 if not re.match('%s$' % NAME_RE, slotName):
315 raise METALError("invalid slot name: %s" % `slotName`, 403 raise METALError("invalid slot name: %s" % `slotName`,
336 m = re.search(r"\s+\Z", text) 424 m = re.search(r"\s+\Z", text)
337 if m: 425 if m:
338 self.program[i] = ("rawtext", text[:m.start()]) 426 self.program[i] = ("rawtext", text[:m.start()])
339 collect.append(m.group()) 427 collect.append(m.group())
340 collect.reverse() 428 collect.reverse()
341 return string.join(collect, "") 429 return "".join(collect)
342 430
343 def unEmitNewlineWhitespace(self): 431 def unEmitNewlineWhitespace(self):
344 collect = [] 432 collect = []
345 i = len(self.program) 433 i = len(self.program)
346 while i > 0: 434 while i > 0:
355 m = re.match(r"(?s)^(.*)(\n[ \t]*)\Z", text) 443 m = re.match(r"(?s)^(.*)(\n[ \t]*)\Z", text)
356 if not m: 444 if not m:
357 break 445 break
358 text, rest = m.group(1, 2) 446 text, rest = m.group(1, 2)
359 collect.reverse() 447 collect.reverse()
360 rest = rest + string.join(collect, "") 448 rest = rest + "".join(collect)
361 del self.program[i:] 449 del self.program[i:]
362 if text: 450 if text:
363 self.emit("rawtext", text) 451 self.emit("rawtext", text)
364 return rest 452 return rest
365 return None 453 return None
366 454
367 def replaceAttrs(self, attrlist, repldict): 455 def replaceAttrs(self, attrlist, repldict):
456 # Each entry in attrlist starts like (name, value).
457 # Result is (name, value, action, expr, xlat) if there is a
458 # tal:attributes entry for that attribute. Additional attrs
459 # defined only by tal:attributes are added here.
460 #
461 # (name, value, action, expr, xlat)
368 if not repldict: 462 if not repldict:
369 return attrlist 463 return attrlist
370 newlist = [] 464 newlist = []
371 for item in attrlist: 465 for item in attrlist:
372 key = item[0] 466 key = item[0]
373 if repldict.has_key(key): 467 if repldict.has_key(key):
374 item = item[:2] + ("replace", repldict[key]) 468 expr, xlat, msgid = repldict[key]
469 item = item[:2] + ("replace", expr, xlat, msgid)
375 del repldict[key] 470 del repldict[key]
376 newlist.append(item) 471 newlist.append(item)
377 for key, value in repldict.items(): # Add dynamic-only attributes 472 # Add dynamic-only attributes
378 item = (key, None, "insert", value) 473 for key, (expr, xlat, msgid) in repldict.items():
379 newlist.append(item) 474 newlist.append((key, None, "insert", expr, xlat, msgid))
380 return newlist 475 return newlist
381 476
382 def emitStartElement(self, name, attrlist, taldict, metaldict, 477 def emitStartElement(self, name, attrlist, taldict, metaldict, i18ndict,
383 position=(None, None), isend=0): 478 position=(None, None), isend=0):
384 if not taldict and not metaldict: 479 if not taldict and not metaldict and not i18ndict:
385 # Handle the simple, common case 480 # Handle the simple, common case
386 self.emitStartTag(name, attrlist, isend) 481 self.emitStartTag(name, attrlist, isend)
387 self.todoPush({}) 482 self.todoPush({})
388 if isend: 483 if isend:
389 self.emitEndElement(name, isend) 484 self.emitEndElement(name, isend)
390 return 485 return
391 486
392 self.position = position 487 self.position = position
393 for key, value in taldict.items(): 488 for key, value in taldict.items():
394 if key not in KNOWN_TAL_ATTRIBUTES: 489 if key not in TALDefs.KNOWN_TAL_ATTRIBUTES:
395 raise TALError("bad TAL attribute: " + `key`, position) 490 raise TALError("bad TAL attribute: " + `key`, position)
396 if not (value or key == 'omit-tag'): 491 if not (value or key == 'omit-tag'):
397 raise TALError("missing value for TAL attribute: " + 492 raise TALError("missing value for TAL attribute: " +
398 `key`, position) 493 `key`, position)
399 for key, value in metaldict.items(): 494 for key, value in metaldict.items():
400 if key not in KNOWN_METAL_ATTRIBUTES: 495 if key not in TALDefs.KNOWN_METAL_ATTRIBUTES:
401 raise METALError("bad METAL attribute: " + `key`, 496 raise METALError("bad METAL attribute: " + `key`,
402 position) 497 position)
403 if not value: 498 if not value:
404 raise TALError("missing value for METAL attribute: " + 499 raise TALError("missing value for METAL attribute: " +
405 `key`, position) 500 `key`, position)
501 for key, value in i18ndict.items():
502 if key not in TALDefs.KNOWN_I18N_ATTRIBUTES:
503 raise I18NError("bad i18n attribute: " + `key`, position)
504 if not value and key in ("attributes", "data", "id"):
505 raise I18NError("missing value for i18n attribute: " +
506 `key`, position)
406 todo = {} 507 todo = {}
407 defineMacro = metaldict.get("define-macro") 508 defineMacro = metaldict.get("define-macro")
408 useMacro = metaldict.get("use-macro") 509 useMacro = metaldict.get("use-macro")
409 defineSlot = metaldict.get("define-slot") 510 defineSlot = metaldict.get("define-slot")
410 fillSlot = metaldict.get("fill-slot") 511 fillSlot = metaldict.get("fill-slot")
415 replace = taldict.get("replace") 516 replace = taldict.get("replace")
416 attrsubst = taldict.get("attributes") 517 attrsubst = taldict.get("attributes")
417 onError = taldict.get("on-error") 518 onError = taldict.get("on-error")
418 omitTag = taldict.get("omit-tag") 519 omitTag = taldict.get("omit-tag")
419 TALtag = taldict.get("tal tag") 520 TALtag = taldict.get("tal tag")
521 i18nattrs = i18ndict.get("attributes")
522 # Preserve empty string if implicit msgids are used. We'll generate
523 # code with the msgid='' and calculate the right implicit msgid during
524 # interpretation phase.
525 msgid = i18ndict.get("translate")
526 varname = i18ndict.get('name')
527 i18ndata = i18ndict.get('data')
528
529 if varname and not self.i18nLevel:
530 raise I18NError(
531 "i18n:name can only occur inside a translation unit",
532 position)
533
534 if i18ndata and not msgid:
535 raise I18NError("i18n:data must be accompanied by i18n:translate",
536 position)
537
420 if len(metaldict) > 1 and (defineMacro or useMacro): 538 if len(metaldict) > 1 and (defineMacro or useMacro):
421 raise METALError("define-macro and use-macro cannot be used " 539 raise METALError("define-macro and use-macro cannot be used "
422 "together or with define-slot or fill-slot", 540 "together or with define-slot or fill-slot",
423 position) 541 position)
424 if content and replace: 542 if replace:
425 raise TALError("content and replace are mutually exclusive", 543 if content:
426 position) 544 raise TALError(
545 "tal:content and tal:replace are mutually exclusive",
546 position)
547 if msgid is not None:
548 raise I18NError(
549 "i18n:translate and tal:replace are mutually exclusive",
550 position)
427 551
428 repeatWhitespace = None 552 repeatWhitespace = None
429 if repeat: 553 if repeat:
430 # Hack to include preceding whitespace in the loop program 554 # Hack to include preceding whitespace in the loop program
431 repeatWhitespace = self.unEmitNewlineWhitespace() 555 repeatWhitespace = self.unEmitNewlineWhitespace()
439 self.emit("setSourceFile", self.source_file) 563 self.emit("setSourceFile", self.source_file)
440 todo["fillSlot"] = fillSlot 564 todo["fillSlot"] = fillSlot
441 self.inMacroUse = 0 565 self.inMacroUse = 0
442 else: 566 else:
443 if fillSlot: 567 if fillSlot:
444 raise METALError, ("fill-slot must be within a use-macro", 568 raise METALError("fill-slot must be within a use-macro",
445 position) 569 position)
446 if not self.inMacroUse: 570 if not self.inMacroUse:
447 if defineMacro: 571 if defineMacro:
448 self.pushProgram() 572 self.pushProgram()
449 self.emit("version", TAL_VERSION) 573 self.emit("version", TAL_VERSION)
450 self.emit("mode", self.xml and "xml" or "html") 574 self.emit("mode", self.xml and "xml" or "html")
457 self.pushProgram() 581 self.pushProgram()
458 todo["useMacro"] = useMacro 582 todo["useMacro"] = useMacro
459 self.inMacroUse = 1 583 self.inMacroUse = 1
460 if defineSlot: 584 if defineSlot:
461 if not self.inMacroDef: 585 if not self.inMacroDef:
462 raise METALError, ( 586 raise METALError(
463 "define-slot must be within a define-macro", 587 "define-slot must be within a define-macro",
464 position) 588 position)
465 self.pushProgram() 589 self.pushProgram()
466 todo["defineSlot"] = defineSlot 590 todo["defineSlot"] = defineSlot
467 591
468 if taldict: 592 if defineSlot or i18ndict:
593
594 domain = i18ndict.get("domain") or self.i18nContext.domain
595 source = i18ndict.get("source") or self.i18nContext.source
596 target = i18ndict.get("target") or self.i18nContext.target
597 if ( domain != DEFAULT_DOMAIN
598 or source is not None
599 or target is not None):
600 self.i18nContext = TranslationContext(self.i18nContext,
601 domain=domain,
602 source=source,
603 target=target)
604 self.emit("beginI18nContext",
605 {"domain": domain, "source": source,
606 "target": target})
607 todo["i18ncontext"] = 1
608 if taldict or i18ndict:
469 dict = {} 609 dict = {}
470 for item in attrlist: 610 for item in attrlist:
471 key, value = item[:2] 611 key, value = item[:2]
472 dict[key] = value 612 dict[key] = value
473 self.emit("beginScope", dict) 613 self.emit("beginScope", dict)
491 todo["repeat"] = repeat 631 todo["repeat"] = repeat
492 self.pushProgram() 632 self.pushProgram()
493 if repeatWhitespace: 633 if repeatWhitespace:
494 self.emitText(repeatWhitespace) 634 self.emitText(repeatWhitespace)
495 if content: 635 if content:
496 todo["content"] = content 636 if varname:
497 if replace: 637 todo['i18nvar'] = (varname, I18N_CONTENT, None)
498 todo["replace"] = replace 638 todo["content"] = content
639 self.pushProgram()
640 else:
641 todo["content"] = content
642 elif replace:
643 # tal:replace w/ i18n:name has slightly different semantics. What
644 # we're actually replacing then is the contents of the ${name}
645 # placeholder.
646 if varname:
647 todo['i18nvar'] = (varname, I18N_EXPRESSION, replace)
648 else:
649 todo["replace"] = replace
499 self.pushProgram() 650 self.pushProgram()
651 # i18n:name w/o tal:replace uses the content as the interpolation
652 # dictionary values
653 elif varname:
654 todo['i18nvar'] = (varname, I18N_REPLACE, None)
655 self.pushProgram()
656 if msgid is not None:
657 self.i18nLevel += 1
658 todo['msgid'] = msgid
659 if i18ndata:
660 todo['i18ndata'] = i18ndata
500 optTag = omitTag is not None or TALtag 661 optTag = omitTag is not None or TALtag
501 if optTag: 662 if optTag:
502 todo["optional tag"] = omitTag, TALtag 663 todo["optional tag"] = omitTag, TALtag
503 self.pushProgram() 664 self.pushProgram()
504 if attrsubst: 665 if attrsubst or i18nattrs:
505 repldict = parseAttributeReplacements(attrsubst) 666 if attrsubst:
667 repldict = TALDefs.parseAttributeReplacements(attrsubst,
668 self.xml)
669 else:
670 repldict = {}
671 if i18nattrs:
672 i18nattrs = _parseI18nAttributes(i18nattrs, attrlist, repldict,
673 self.position, self.xml,
674 self.source_file)
675 else:
676 i18nattrs = {}
677 # Convert repldict's name-->expr mapping to a
678 # name-->(compiled_expr, translate) mapping
506 for key, value in repldict.items(): 679 for key, value in repldict.items():
507 repldict[key] = self.compileExpression(value) 680 if i18nattrs.get(key, None):
681 raise I18NError(
682 ("attribute [%s] cannot both be part of tal:attributes" +
683 " and have a msgid in i18n:attributes") % key,
684 position)
685 ce = self.compileExpression(value)
686 repldict[key] = ce, key in i18nattrs, i18nattrs.get(key)
687 for key in i18nattrs:
688 if not repldict.has_key(key):
689 repldict[key] = None, 1, i18nattrs.get(key)
508 else: 690 else:
509 repldict = {} 691 repldict = {}
510 if replace: 692 if replace:
511 todo["repldict"] = repldict 693 todo["repldict"] = repldict
512 repldict = {} 694 repldict = {}
513 self.emitStartTag(name, self.replaceAttrs(attrlist, repldict), isend) 695 self.emitStartTag(name, self.replaceAttrs(attrlist, repldict), isend)
514 if optTag: 696 if optTag:
515 self.pushProgram() 697 self.pushProgram()
516 if content: 698 if content and not varname:
699 self.pushProgram()
700 if msgid is not None:
701 self.pushProgram()
702 if content and varname:
517 self.pushProgram() 703 self.pushProgram()
518 if todo and position != (None, None): 704 if todo and position != (None, None):
519 todo["position"] = position 705 todo["position"] = position
520 self.todoPush(todo) 706 self.todoPush(todo)
521 if isend: 707 if isend:
541 onError = todo.get("onError") 727 onError = todo.get("onError")
542 define = todo.get("define") 728 define = todo.get("define")
543 repldict = todo.get("repldict", {}) 729 repldict = todo.get("repldict", {})
544 scope = todo.get("scope") 730 scope = todo.get("scope")
545 optTag = todo.get("optional tag") 731 optTag = todo.get("optional tag")
732 msgid = todo.get('msgid')
733 i18ncontext = todo.get("i18ncontext")
734 varname = todo.get('i18nvar')
735 i18ndata = todo.get('i18ndata')
546 736
547 if implied > 0: 737 if implied > 0:
548 if defineMacro or useMacro or defineSlot or fillSlot: 738 if defineMacro or useMacro or defineSlot or fillSlot:
549 exc = METALError 739 exc = METALError
550 what = "METAL" 740 what = "METAL"
552 exc = TALError 742 exc = TALError
553 what = "TAL" 743 what = "TAL"
554 raise exc("%s attributes on <%s> require explicit </%s>" % 744 raise exc("%s attributes on <%s> require explicit </%s>" %
555 (what, name, name), position) 745 (what, name, name), position)
556 746
747 # If there's no tal:content or tal:replace in the tag with the
748 # i18n:name, tal:replace is the default.
557 if content: 749 if content:
558 self.emitSubstitution(content, {}) 750 self.emitSubstitution(content, {})
751 # If we're looking at an implicit msgid, emit the insertTranslation
752 # opcode now, so that the end tag doesn't become part of the implicit
753 # msgid. If we're looking at an explicit msgid, it's better to emit
754 # the opcode after the i18nVariable opcode so we can better handle
755 # tags with both of them in them (and in the latter case, the contents
756 # would be thrown away for msgid purposes).
757 #
758 # Still, we should emit insertTranslation opcode before i18nVariable
759 # in case tal:content, i18n:translate and i18n:name in the same tag
760 if msgid is not None:
761 if (not varname) or (
762 varname and (varname[1] == I18N_CONTENT)):
763 self.emitTranslation(msgid, i18ndata)
764 self.i18nLevel -= 1
559 if optTag: 765 if optTag:
560 self.emitOptTag(name, optTag, isend) 766 self.emitOptTag(name, optTag, isend)
561 elif not isend: 767 elif not isend:
768 # If we're processing the end tag for a tag that contained
769 # i18n:name, we need to make sure that optimize() won't collect
770 # immediately following end tags into the same rawtextOffset, so
771 # put a spacer here that the optimizer will recognize.
772 if varname:
773 self.emit('noop')
562 self.emitEndTag(name) 774 self.emitEndTag(name)
775 # If i18n:name appeared in the same tag as tal:replace then we're
776 # going to do the substitution a little bit differently. The results
777 # of the expression go into the i18n substitution dictionary.
563 if replace: 778 if replace:
564 self.emitSubstitution(replace, repldict) 779 self.emitSubstitution(replace, repldict)
780 elif varname:
781 # o varname[0] is the variable name
782 # o varname[1] is either
783 # - I18N_REPLACE for implicit tal:replace
784 # - I18N_CONTENT for tal:content
785 # - I18N_EXPRESSION for explicit tal:replace
786 # o varname[2] will be None for the first two actions and the
787 # replacement tal expression for the third action.
788 assert (varname[1]
789 in [I18N_REPLACE, I18N_CONTENT, I18N_EXPRESSION])
790 self.emitI18nVariable(varname)
791 # Do not test for "msgid is not None", i.e. we only want to test for
792 # explicit msgids here. See comment above.
793 if msgid is not None:
794 # in case tal:content, i18n:translate and i18n:name in the
795 # same tag insertTranslation opcode has already been
796 # emitted
797 if varname and (varname[1] <> I18N_CONTENT):
798 self.emitTranslation(msgid, i18ndata)
565 if repeat: 799 if repeat:
566 self.emitRepeat(repeat) 800 self.emitRepeat(repeat)
567 if condition: 801 if condition:
568 self.emitCondition(condition) 802 self.emitCondition(condition)
569 if onError: 803 if onError:
570 self.emitOnError(name, onError, optTag and optTag[1], isend) 804 self.emitOnError(name, onError, optTag and optTag[1], isend)
571 if scope: 805 if scope:
572 self.emit("endScope") 806 self.emit("endScope")
807 if i18ncontext:
808 self.emit("endI18nContext")
809 assert self.i18nContext.parent is not None
810 self.i18nContext = self.i18nContext.parent
573 if defineSlot: 811 if defineSlot:
574 self.emitDefineSlot(defineSlot) 812 self.emitDefineSlot(defineSlot)
575 if fillSlot: 813 if fillSlot:
576 self.emitFillSlot(fillSlot) 814 self.emitFillSlot(fillSlot)
577 if useMacro: 815 if useMacro:
578 self.emitUseMacro(useMacro) 816 self.emitUseMacro(useMacro)
579 if defineMacro: 817 if defineMacro:
580 self.emitDefineMacro(defineMacro) 818 self.emitDefineMacro(defineMacro)
581 819
820
821 def _parseI18nAttributes(i18nattrs, attrlist, repldict, position,
822 xml, source_file):
823
824 def addAttribute(dic, attr, msgid, position, xml):
825 if not xml:
826 attr = attr.lower()
827 if attr in dic:
828 raise TALError(
829 "attribute may only be specified once in i18n:attributes: "
830 + attr,
831 position)
832 dic[attr] = msgid
833
834 d = {}
835 if ';' in i18nattrs:
836 i18nattrlist = i18nattrs.split(';')
837 i18nattrlist = [attr.strip().split()
838 for attr in i18nattrlist if attr.strip()]
839 for parts in i18nattrlist:
840 if len(parts) > 2:
841 raise TALError("illegal i18n:attributes specification: %r"
842 % parts, position)
843 if len(parts) == 2:
844 attr, msgid = parts
845 else:
846 # len(parts) == 1
847 attr = parts[0]
848 msgid = None
849 addAttribute(d, attr, msgid, position, xml)
850 else:
851 i18nattrlist = i18nattrs.split()
852 if len(i18nattrlist) == 2:
853 staticattrs = [attr[0] for attr in attrlist if len(attr) == 2]
854 if (not i18nattrlist[1] in staticattrs) and (
855 not i18nattrlist[1] in repldict):
856 attr, msgid = i18nattrlist
857 addAttribute(d, attr, msgid, position, xml)
858 else:
859 msgid = None
860 for attr in i18nattrlist:
861 addAttribute(d, attr, msgid, position, xml)
862 else:
863 msgid = None
864 for attr in i18nattrlist:
865 addAttribute(d, attr, msgid, position, xml)
866 return d
867
582 def test(): 868 def test():
583 t = TALGenerator() 869 t = TALGenerator()
584 t.pushProgram() 870 t.pushProgram()
585 t.emit("bar") 871 t.emit("bar")
586 p = t.popProgram() 872 p = t.popProgram()

Roundup Issue Tracker: http://roundup-tracker.org/