diff options
Diffstat (limited to 'src/zope/tal/talgenerator.py')
-rw-r--r-- | src/zope/tal/talgenerator.py | 856 |
1 files changed, 856 insertions, 0 deletions
diff --git a/src/zope/tal/talgenerator.py b/src/zope/tal/talgenerator.py new file mode 100644 index 0000000..e01bd50 --- /dev/null +++ b/src/zope/tal/talgenerator.py @@ -0,0 +1,856 @@ +############################################################################## +# +# Copyright (c) 2001, 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Code generator for TALInterpreter intermediate code. + +$Id$ +""" +import cgi +import re + +from zope.tal import taldefs +from zope.tal.taldefs import NAME_RE, TAL_VERSION +from zope.tal.taldefs import I18NError, METALError, TALError +from zope.tal.taldefs import parseSubstitution +from zope.tal.translationcontext import TranslationContext, DEFAULT_DOMAIN + + +_name_rx = re.compile(NAME_RE) + +class TALGenerator(object): + + inMacroUse = 0 + inMacroDef = 0 + source_file = None + + def __init__(self, expressionCompiler=None, xml=1, source_file=None): + if not expressionCompiler: + from zope.tal.dummyengine import DummyEngine + expressionCompiler = DummyEngine() + self.expressionCompiler = expressionCompiler + self.CompilerError = expressionCompiler.getCompilerError() + # This holds the emitted opcodes representing the input + self.program = [] + # The program stack for when we need to do some sub-evaluation for an + # intermediate result. E.g. in an i18n:name tag for which the + # contents describe the ${name} value. + self.stack = [] + # Another stack of postponed actions. Elements on this stack are a + # dictionary; key/values contain useful information that + # emitEndElement needs to finish its calculations + self.todoStack = [] + self.macros = {} + # {slot-name --> default content program} + self.slots = {} + self.slotStack = [] + self.xml = xml # true --> XML, false --> HTML + self.emit("version", TAL_VERSION) + self.emit("mode", xml and "xml" or "html") + if source_file is not None: + self.source_file = source_file + self.emit("setSourceFile", source_file) + self.i18nContext = TranslationContext() + self.i18nLevel = 0 + + def getCode(self): + assert not self.stack + assert not self.todoStack + return self.optimize(self.program), self.macros + + def optimize(self, program): + output = [] + collect = [] + cursor = 0 + for cursor in xrange(len(program)+1): + try: + item = program[cursor] + except IndexError: + item = (None, None) + opcode = item[0] + if opcode == "rawtext": + collect.append(item[1]) + continue + if opcode == "endTag": + collect.append("</%s>" % item[1]) + continue + if opcode == "startTag": + if self.optimizeStartTag(collect, item[1], item[2], ">"): + continue + if opcode == "startEndTag": + endsep = self.xml and "/>" or " />" + if self.optimizeStartTag(collect, item[1], item[2], endsep): + continue + if opcode in ("beginScope", "endScope"): + # Push *Scope instructions in front of any text instructions; + # this allows text instructions separated only by *Scope + # instructions to be joined together. + output.append(self.optimizeArgsList(item)) + continue + if opcode == 'noop': + # This is a spacer for end tags in the face of i18n:name + # attributes. We can't let the optimizer collect immediately + # following end tags into the same rawtextOffset. + opcode = None + pass + text = "".join(collect) + if text: + i = text.rfind("\n") + if i >= 0: + i = len(text) - (i + 1) + output.append(("rawtextColumn", (text, i))) + else: + output.append(("rawtextOffset", (text, len(text)))) + if opcode != None: + output.append(self.optimizeArgsList(item)) + collect = [] + return self.optimizeCommonTriple(output) + + def optimizeArgsList(self, item): + if len(item) == 2: + return item + else: + return item[0], tuple(item[1:]) + + # These codes are used to indicate what sort of special actions + # are needed for each special attribute. (Simple attributes don't + # get action codes.) + # + # The special actions (which are modal) are handled by + # TALInterpreter.attrAction() and .attrAction_tal(). + # + # Each attribute is represented by a tuple: + # + # (name, value) -- a simple name/value pair, with + # no special processing + # + # (name, value, action, *extra) -- attribute with special + # processing needs, action is a + # code that indicates which + # branch to take, and *extra + # contains additional, + # action-specific information + # needed by the processing + # + def optimizeStartTag(self, collect, name, attrlist, end): + # return true if the tag can be converted to plain text + if not attrlist: + collect.append("<%s%s" % (name, end)) + return 1 + opt = 1 + new = ["<" + name] + for i in range(len(attrlist)): + item = attrlist[i] + if len(item) > 2: + opt = 0 + name, value, action = item[:3] + attrlist[i] = (name, value, action) + item[3:] + else: + if item[1] is None: + s = item[0] + else: + s = '%s="%s"' % (item[0], taldefs.attrEscape(item[1])) + attrlist[i] = item[0], s + new.append(" " + s) + # if no non-optimizable attributes were found, convert to plain text + if opt: + new.append(end) + collect.extend(new) + return opt + + def optimizeCommonTriple(self, program): + if len(program) < 3: + return program + output = program[:2] + prev2, prev1 = output + for item in program[2:]: + if ( item[0] == "beginScope" + and prev1[0] == "setPosition" + and prev2[0] == "rawtextColumn"): + position = output.pop()[1] + text, column = output.pop()[1] + prev1 = None, None + closeprev = 0 + if output and output[-1][0] == "endScope": + closeprev = 1 + output.pop() + item = ("rawtextBeginScope", + (text, column, position, closeprev, item[1])) + output.append(item) + prev2 = prev1 + prev1 = item + return output + + def todoPush(self, todo): + self.todoStack.append(todo) + + def todoPop(self): + return self.todoStack.pop() + + def compileExpression(self, expr): + try: + return self.expressionCompiler.compile(expr) + except self.CompilerError, err: + raise TALError('%s in expression %s' % (err.args[0], `expr`), + self.position) + + def pushProgram(self): + self.stack.append(self.program) + self.program = [] + + def popProgram(self): + program = self.program + self.program = self.stack.pop() + return self.optimize(program) + + def pushSlots(self): + self.slotStack.append(self.slots) + self.slots = {} + + def popSlots(self): + slots = self.slots + self.slots = self.slotStack.pop() + return slots + + def emit(self, *instruction): + self.program.append(instruction) + + def emitStartTag(self, name, attrlist, isend=0): + if isend: + opcode = "startEndTag" + else: + opcode = "startTag" + self.emit(opcode, name, attrlist) + + def emitEndTag(self, name): + if self.xml and self.program and self.program[-1][0] == "startTag": + # Minimize empty element + self.program[-1] = ("startEndTag",) + self.program[-1][1:] + else: + self.emit("endTag", name) + + def emitOptTag(self, name, optTag, isend): + program = self.popProgram() #block + start = self.popProgram() #start tag + if (isend or not program) and self.xml: + # Minimize empty element + start[-1] = ("startEndTag",) + start[-1][1:] + isend = 1 + cexpr = optTag[0] + if cexpr: + cexpr = self.compileExpression(optTag[0]) + self.emit("optTag", name, cexpr, optTag[1], isend, start, program) + + def emitRawText(self, text): + self.emit("rawtext", text) + + def emitText(self, text): + self.emitRawText(cgi.escape(text)) + + def emitDefines(self, defines): + for part in taldefs.splitParts(defines): + m = re.match( + r"(?s)\s*(?:(global|local)\s+)?(%s)\s+(.*)\Z" % NAME_RE, part) + if not m: + raise TALError("invalid define syntax: " + `part`, + self.position) + scope, name, expr = m.group(1, 2, 3) + scope = scope or "local" + cexpr = self.compileExpression(expr) + if scope == "local": + self.emit("setLocal", name, cexpr) + else: + self.emit("setGlobal", name, cexpr) + + def emitOnError(self, name, onError, TALtag, isend): + block = self.popProgram() + key, expr = parseSubstitution(onError) + cexpr = self.compileExpression(expr) + if key == "text": + self.emit("insertText", cexpr, []) + else: + assert key == "structure" + self.emit("insertStructure", cexpr, {}, []) + if TALtag: + self.emitOptTag(name, (None, 1), isend) + else: + self.emitEndTag(name) + handler = self.popProgram() + self.emit("onError", block, handler) + + def emitCondition(self, expr): + cexpr = self.compileExpression(expr) + program = self.popProgram() + self.emit("condition", cexpr, program) + + def emitRepeat(self, arg): + m = re.match("(?s)\s*(%s)\s+(.*)\Z" % NAME_RE, arg) + if not m: + raise TALError("invalid repeat syntax: " + `arg`, + self.position) + name, expr = m.group(1, 2) + cexpr = self.compileExpression(expr) + program = self.popProgram() + self.emit("loop", name, cexpr, program) + + def emitSubstitution(self, arg, attrDict={}): + key, expr = parseSubstitution(arg) + cexpr = self.compileExpression(expr) + program = self.popProgram() + if key == "text": + self.emit("insertText", cexpr, program) + else: + assert key == "structure" + self.emit("insertStructure", cexpr, attrDict, program) + + def emitI18nSubstitution(self, arg, attrDict={}): + # TODO: Code duplication is BAD, we need to fix it later + key, expr = parseSubstitution(arg) + cexpr = self.compileExpression(expr) + program = self.popProgram() + if key == "text": + self.emit("insertI18nText", cexpr, program) + else: + assert key == "structure" + self.emit("insertI18nStructure", cexpr, attrDict, program) + + def emitEvaluateCode(self, lang): + program = self.popProgram() + self.emit('evaluateCode', lang, program) + + def emitI18nVariable(self, varname): + # Used for i18n:name attributes. + m = _name_rx.match(varname) + if m is None or m.group() != varname: + raise TALError("illegal i18n:name: %r" % varname, self.position) + program = self.popProgram() + self.emit('i18nVariable', varname, program, None, False) + + def emitTranslation(self, msgid, i18ndata): + program = self.popProgram() + if i18ndata is None: + self.emit('insertTranslation', msgid, program) + else: + key, expr = parseSubstitution(i18ndata) + cexpr = self.compileExpression(expr) + assert key == 'text' + self.emit('insertTranslation', msgid, program, cexpr) + + def emitDefineMacro(self, macroName): + program = self.popProgram() + macroName = macroName.strip() + if self.macros.has_key(macroName): + raise METALError("duplicate macro definition: %s" % `macroName`, + self.position) + if not re.match('%s$' % NAME_RE, macroName): + raise METALError("invalid macro name: %s" % `macroName`, + self.position) + self.macros[macroName] = program + self.inMacroDef = self.inMacroDef - 1 + self.emit("defineMacro", macroName, program) + + def emitUseMacro(self, expr): + cexpr = self.compileExpression(expr) + program = self.popProgram() + self.inMacroUse = 0 + self.emit("useMacro", expr, cexpr, self.popSlots(), program) + + def emitExtendMacro(self, defineName, useExpr): + cexpr = self.compileExpression(useExpr) + program = self.popProgram() + self.inMacroUse = 0 + self.emit("extendMacro", useExpr, cexpr, self.popSlots(), program, + defineName) + self.emitDefineMacro(defineName) + + def emitDefineSlot(self, slotName): + program = self.popProgram() + slotName = slotName.strip() + if not re.match('%s$' % NAME_RE, slotName): + raise METALError("invalid slot name: %s" % `slotName`, + self.position) + self.emit("defineSlot", slotName, program) + + def emitFillSlot(self, slotName): + program = self.popProgram() + slotName = slotName.strip() + if self.slots.has_key(slotName): + raise METALError("duplicate fill-slot name: %s" % `slotName`, + self.position) + if not re.match('%s$' % NAME_RE, slotName): + raise METALError("invalid slot name: %s" % `slotName`, + self.position) + self.slots[slotName] = program + self.inMacroUse = 1 + self.emit("fillSlot", slotName, program) + + def unEmitWhitespace(self): + collect = [] + i = len(self.program) - 1 + while i >= 0: + item = self.program[i] + if item[0] != "rawtext": + break + text = item[1] + if not re.match(r"\A\s*\Z", text): + break + collect.append(text) + i = i-1 + del self.program[i+1:] + if i >= 0 and self.program[i][0] == "rawtext": + text = self.program[i][1] + m = re.search(r"\s+\Z", text) + if m: + self.program[i] = ("rawtext", text[:m.start()]) + collect.append(m.group()) + collect.reverse() + return "".join(collect) + + def unEmitNewlineWhitespace(self): + collect = [] + i = len(self.program) + while i > 0: + i = i-1 + item = self.program[i] + if item[0] != "rawtext": + break + text = item[1] + if re.match(r"\A[ \t]*\Z", text): + collect.append(text) + continue + m = re.match(r"(?s)^(.*)(\n[ \t]*)\Z", text) + if not m: + break + text, rest = m.group(1, 2) + collect.reverse() + rest = rest + "".join(collect) + del self.program[i:] + if text: + self.emit("rawtext", text) + return rest + return None + + def replaceAttrs(self, attrlist, repldict): + # Each entry in attrlist starts like (name, value). Result is + # (name, value, action, expr, xlat, msgid) if there is a + # tal:attributes entry for that attribute. Additional attrs + # defined only by tal:attributes are added here. + # + # (name, value, action, expr, xlat, msgid) + if not repldict: + return attrlist + newlist = [] + for item in attrlist: + key = item[0] + if repldict.has_key(key): + expr, xlat, msgid = repldict[key] + item = item[:2] + ("replace", expr, xlat, msgid) + del repldict[key] + newlist.append(item) + # Add dynamic-only attributes + for key, (expr, xlat, msgid) in repldict.items(): + newlist.append((key, None, "insert", expr, xlat, msgid)) + return newlist + + def emitStartElement(self, name, attrlist, taldict, metaldict, i18ndict, + position=(None, None), isend=0): + if not taldict and not metaldict and not i18ndict: + # Handle the simple, common case + self.emitStartTag(name, attrlist, isend) + self.todoPush({}) + if isend: + self.emitEndElement(name, isend) + return + self.position = position + + # TODO: Ugly hack to work around tal:replace and i18n:translate issue. + # I (DV) need to cleanup the code later. + replaced = False + if "replace" in taldict: + if "content" in taldict: + raise TALError( + "tal:content and tal:replace are mutually exclusive", + position) + taldict["omit-tag"] = taldict.get("omit-tag", "") + taldict["content"] = taldict.pop("replace") + replaced = True + + for key, value in taldict.items(): + if key not in taldefs.KNOWN_TAL_ATTRIBUTES: + raise TALError("bad TAL attribute: " + `key`, position) + if not (value or key == 'omit-tag'): + raise TALError("missing value for TAL attribute: " + + `key`, position) + for key, value in metaldict.items(): + if key not in taldefs.KNOWN_METAL_ATTRIBUTES: + raise METALError("bad METAL attribute: " + `key`, + position) + if not value: + raise TALError("missing value for METAL attribute: " + + `key`, position) + for key, value in i18ndict.items(): + if key not in taldefs.KNOWN_I18N_ATTRIBUTES: + raise I18NError("bad i18n attribute: " + `key`, position) + if not value and key in ("attributes", "data", "id"): + raise I18NError("missing value for i18n attribute: " + + `key`, position) + + todo = {} + defineMacro = metaldict.get("define-macro") + extendMacro = metaldict.get("extend-macro") + useMacro = metaldict.get("use-macro") + defineSlot = metaldict.get("define-slot") + fillSlot = metaldict.get("fill-slot") + define = taldict.get("define") + condition = taldict.get("condition") + repeat = taldict.get("repeat") + content = taldict.get("content") + script = taldict.get("script") + attrsubst = taldict.get("attributes") + onError = taldict.get("on-error") + omitTag = taldict.get("omit-tag") + TALtag = taldict.get("tal tag") + i18nattrs = i18ndict.get("attributes") + # Preserve empty string if implicit msgids are used. We'll generate + # code with the msgid='' and calculate the right implicit msgid during + # interpretation phase. + msgid = i18ndict.get("translate") + varname = i18ndict.get('name') + i18ndata = i18ndict.get('data') + + if varname and not self.i18nLevel: + raise I18NError( + "i18n:name can only occur inside a translation unit", + position) + + if i18ndata and not msgid: + raise I18NError("i18n:data must be accompanied by i18n:translate", + position) + + if extendMacro: + if useMacro: + raise METALError( + "extend-macro cannot be used with use-macro", position) + if not defineMacro: + raise METALError( + "extend-macro must be used with define-macro", position) + + if defineMacro or extendMacro or useMacro: + if fillSlot or defineSlot: + raise METALError( + "define-slot and fill-slot cannot be used with " + "define-macro, extend-macro, or use-macro", position) + if defineMacro and useMacro: + raise METALError( + "define-macro may not be used with use-macro", position) + + useMacro = useMacro or extendMacro + + if content and msgid: + raise I18NError( + "explicit message id and tal:content can't be used together", + position) + + repeatWhitespace = None + if repeat: + # Hack to include preceding whitespace in the loop program + repeatWhitespace = self.unEmitNewlineWhitespace() + if position != (None, None): + # TODO: at some point we should insist on a non-trivial position + self.emit("setPosition", position) + if self.inMacroUse: + if fillSlot: + self.pushProgram() + # generate a source annotation at the beginning of fill-slot + if self.source_file is not None: + if position != (None, None): + self.emit("setPosition", position) + self.emit("setSourceFile", self.source_file) + todo["fillSlot"] = fillSlot + self.inMacroUse = 0 + else: + if fillSlot: + raise METALError("fill-slot must be within a use-macro", + position) + if not self.inMacroUse: + if defineMacro: + self.pushProgram() + self.emit("version", TAL_VERSION) + self.emit("mode", self.xml and "xml" or "html") + # generate a source annotation at the beginning of the macro + if self.source_file is not None: + if position != (None, None): + self.emit("setPosition", position) + self.emit("setSourceFile", self.source_file) + todo["defineMacro"] = defineMacro + self.inMacroDef = self.inMacroDef + 1 + if useMacro: + self.pushSlots() + self.pushProgram() + todo["useMacro"] = useMacro + self.inMacroUse = 1 + if defineSlot: + if not self.inMacroDef: + raise METALError( + "define-slot must be within a define-macro", + position) + self.pushProgram() + todo["defineSlot"] = defineSlot + + if defineSlot or i18ndict: + + domain = i18ndict.get("domain") or self.i18nContext.domain + source = i18ndict.get("source") or self.i18nContext.source + target = i18ndict.get("target") or self.i18nContext.target + if ( domain != DEFAULT_DOMAIN + or source is not None + or target is not None): + self.i18nContext = TranslationContext(self.i18nContext, + domain=domain, + source=source, + target=target) + self.emit("beginI18nContext", + {"domain": domain, "source": source, + "target": target}) + todo["i18ncontext"] = 1 + if taldict or i18ndict: + dict = {} + for item in attrlist: + key, value = item[:2] + dict[key] = value + self.emit("beginScope", dict) + todo["scope"] = 1 + if onError: + self.pushProgram() # handler + if TALtag: + self.pushProgram() # start + self.emitStartTag(name, list(attrlist)) # Must copy attrlist! + if TALtag: + self.pushProgram() # start + self.pushProgram() # block + todo["onError"] = onError + if define: + self.emitDefines(define) + todo["define"] = define + if condition: + self.pushProgram() + todo["condition"] = condition + if repeat: + todo["repeat"] = repeat + self.pushProgram() + if repeatWhitespace: + self.emitText(repeatWhitespace) + if content: + if varname: + todo['i18nvar'] = varname + todo["content"] = content + self.pushProgram() + else: + todo["content"] = content + # i18n:name w/o tal:replace uses the content as the interpolation + # dictionary values + elif varname: + todo['i18nvar'] = varname + self.pushProgram() + if msgid is not None: + self.i18nLevel += 1 + todo['msgid'] = msgid + if i18ndata: + todo['i18ndata'] = i18ndata + optTag = omitTag is not None or TALtag + if optTag: + todo["optional tag"] = omitTag, TALtag + self.pushProgram() + if attrsubst or i18nattrs: + if attrsubst: + repldict = taldefs.parseAttributeReplacements(attrsubst, + self.xml) + else: + repldict = {} + if i18nattrs: + i18nattrs = _parseI18nAttributes(i18nattrs, self.position, + self.xml) + else: + i18nattrs = {} + # Convert repldict's name-->expr mapping to a + # name-->(compiled_expr, translate) mapping + for key, value in repldict.items(): + if i18nattrs.get(key, None): + raise I18NError( + "attribute [%s] cannot both be part of tal:attributes" + " and have a msgid in i18n:attributes" % key, + position) + ce = self.compileExpression(value) + repldict[key] = ce, key in i18nattrs, i18nattrs.get(key) + for key in i18nattrs: + if key not in repldict: + repldict[key] = None, 1, i18nattrs.get(key) + else: + repldict = {} + if replaced: + todo["repldict"] = repldict + repldict = {} + if script: + todo["script"] = script + self.emitStartTag(name, self.replaceAttrs(attrlist, repldict), isend) + if optTag: + self.pushProgram() + if content and not varname: + self.pushProgram() + if not content and msgid is not None: + self.pushProgram() + if content and varname: + self.pushProgram() + if script: + self.pushProgram() + if todo and position != (None, None): + todo["position"] = position + self.todoPush(todo) + if isend: + self.emitEndElement(name, isend, position=position) + + def emitEndElement(self, name, isend=0, implied=0, position=(None, None)): + todo = self.todoPop() + if not todo: + # Shortcut + if not isend: + self.emitEndTag(name) + return + + self.position = todo.get("position", (None, None)) + defineMacro = todo.get("defineMacro") + useMacro = todo.get("useMacro") + defineSlot = todo.get("defineSlot") + fillSlot = todo.get("fillSlot") + repeat = todo.get("repeat") + content = todo.get("content") + script = todo.get("script") + condition = todo.get("condition") + onError = todo.get("onError") + repldict = todo.get("repldict", {}) + scope = todo.get("scope") + optTag = todo.get("optional tag") + msgid = todo.get('msgid') + i18ncontext = todo.get("i18ncontext") + varname = todo.get('i18nvar') + i18ndata = todo.get('i18ndata') + + if implied > 0: + if defineMacro or useMacro or defineSlot or fillSlot: + exc = METALError + what = "METAL" + else: + exc = TALError + what = "TAL" + raise exc("%s attributes on <%s> require explicit </%s>" % + (what, name, name), self.position) + + if script: + self.emitEvaluateCode(script) + # If there's no tal:content or tal:replace in the tag with the + # i18n:name, tal:replace is the default. + if content: + if msgid is not None: + self.emitI18nSubstitution(content, repldict) + else: + self.emitSubstitution(content, repldict) + # If we're looking at an implicit msgid, emit the insertTranslation + # opcode now, so that the end tag doesn't become part of the implicit + # msgid. If we're looking at an explicit msgid, it's better to emit + # the opcode after the i18nVariable opcode so we can better handle + # tags with both of them in them (and in the latter case, the contents + # would be thrown away for msgid purposes). + # + # Still, we should emit insertTranslation opcode before i18nVariable + # in case tal:content, i18n:translate and i18n:name in the same tag + if not content and msgid is not None: + self.emitTranslation(msgid, i18ndata) + self.i18nLevel -= 1 + if optTag: + self.emitOptTag(name, optTag, isend) + elif not isend: + # If we're processing the end tag for a tag that contained + # i18n:name, we need to make sure that optimize() won't collect + # immediately following end tags into the same rawtextOffset, so + # put a spacer here that the optimizer will recognize. + if varname: + self.emit('noop') + self.emitEndTag(name) + if varname: + self.emitI18nVariable(varname) + if repeat: + self.emitRepeat(repeat) + if condition: + self.emitCondition(condition) + if onError: + self.emitOnError(name, onError, optTag and optTag[1], isend) + if scope: + self.emit("endScope") + if i18ncontext: + self.emit("endI18nContext") + assert self.i18nContext.parent is not None + self.i18nContext = self.i18nContext.parent + if defineSlot: + self.emitDefineSlot(defineSlot) + if fillSlot: + self.emitFillSlot(fillSlot) + if useMacro or defineMacro: + if useMacro and defineMacro: + self.emitExtendMacro(defineMacro, useMacro) + elif useMacro: + self.emitUseMacro(useMacro) + elif defineMacro: + self.emitDefineMacro(defineMacro) + if useMacro or defineSlot: + # generate a source annotation after define-slot or use-macro + # because the source file might have changed + if self.source_file is not None: + if position != (None, None): + self.emit("setPosition", position) + self.emit("setSourceFile", self.source_file) + + +def _parseI18nAttributes(i18nattrs, position, xml): + d = {} + # Filter out empty items, eg: + # i18n:attributes="value msgid; name msgid2;" + # would result in 3 items where the last one is empty + attrs = [spec for spec in i18nattrs.split(";") if spec] + for spec in attrs: + parts = spec.split() + if len(parts) == 2: + attr, msgid = parts + elif len(parts) == 1: + attr = parts[0] + msgid = None + else: + raise TALError("illegal i18n:attributes specification: %r" % spec, + position) + if not xml: + attr = attr.lower() + if attr in d: + raise TALError( + "attribute may only be specified once in i18n:attributes: %r" + % attr, + position) + d[attr] = msgid + return d + +def test(): + t = TALGenerator() + t.pushProgram() + t.emit("bar") + p = t.popProgram() + t.emit("foo", p) + +if __name__ == "__main__": + test() |