[Zope-Checkins] CVS: Zope3/lib/python/Zope/TAL - HTMLTALParser.py:1.34 TALGenerator.py:1.56 TALInterpreter.py:1.70 TALParser.py:1.20
Fred L. Drake, Jr.
fdrake@acm.org
Wed, 12 Jun 2002 11:39:37 -0400
Update of /cvs-repository/Zope3/lib/python/Zope/TAL
In directory cvs.zope.org:/tmp/cvs-serv12767
Modified Files:
HTMLTALParser.py TALGenerator.py TALInterpreter.py
TALParser.py
Log Message:
Merge from fdrake-tal-i18n-branch:
This is the bulk of the changes from the branch.
=== Zope3/lib/python/Zope/TAL/HTMLTALParser.py 1.33 => 1.34 ===
from TALGenerator import TALGenerator
-from TALDefs import ZOPE_METAL_NS, ZOPE_TAL_NS, METALError, TALError
from HTMLParser import HTMLParser, HTMLParseError
+from TALDefs import \
+ ZOPE_METAL_NS, ZOPE_TAL_NS, ZOPE_I18N_NS, METALError, TALError
BOOLEAN_HTML_ATTRS = [
# List of Boolean attributes in HTML that may be given in
@@ -106,7 +107,10 @@
self.gen = gen
self.tagstack = []
self.nsstack = []
- self.nsdict = {'tal': ZOPE_TAL_NS, 'metal': ZOPE_METAL_NS}
+ self.nsdict = {'tal': ZOPE_TAL_NS,
+ 'metal': ZOPE_METAL_NS,
+ 'i18n': ZOPE_I18N_NS,
+ }
def parseFile(self, file):
f = open(file)
@@ -132,9 +136,10 @@
def handle_starttag(self, tag, attrs):
self.close_para_tags(tag)
self.scan_xmlns(attrs)
- tag, attrlist, taldict, metaldict = self.process_ns(tag, attrs)
+ tag, attrlist, taldict, metaldict, i18ndict \
+ = self.process_ns(tag, attrs)
self.tagstack.append(tag)
- self.gen.emitStartElement(tag, attrlist, taldict, metaldict,
+ self.gen.emitStartElement(tag, attrlist, taldict, metaldict, i18ndict,
self.getpos())
if tag in EMPTY_HTML_TAGS:
self.implied_endtag(tag, -1)
@@ -142,14 +147,15 @@
def handle_startendtag(self, tag, attrs):
self.close_para_tags(tag)
self.scan_xmlns(attrs)
- tag, attrlist, taldict, metaldict = self.process_ns(tag, attrs)
+ tag, attrlist, taldict, metaldict, i18ndict \
+ = self.process_ns(tag, attrs)
if taldict.get("content"):
self.gen.emitStartElement(tag, attrlist, taldict, metaldict,
- self.getpos())
+ i18ndict, self.getpos())
self.gen.emitEndElement(tag, implied=-1)
else:
self.gen.emitStartElement(tag, attrlist, taldict, metaldict,
- self.getpos(), isend=1)
+ i18ndict, self.getpos(), isend=1)
self.pop_xmlns()
def handle_endtag(self, tag):
@@ -252,7 +258,7 @@
prefix, suffix = name.split(':', 1)
if prefix == 'xmlns':
nsuri = self.nsdict.get(suffix)
- if nsuri in (ZOPE_TAL_NS, ZOPE_METAL_NS):
+ if nsuri in (ZOPE_TAL_NS, ZOPE_METAL_NS, ZOPE_I18N_NS):
return name, name, prefix
else:
nsuri = self.nsdict.get(prefix)
@@ -260,12 +266,15 @@
return name, suffix, 'tal'
elif nsuri == ZOPE_METAL_NS:
return name, suffix, 'metal'
+ elif nsuri == ZOPE_I18N_NS:
+ return name, suffix, 'i18n'
return name, name, 0
def process_ns(self, name, attrs):
attrlist = []
taldict = {}
metaldict = {}
+ i18ndict = {}
name, namebase, namens = self.fixname(name)
for item in attrs:
key, value = item
@@ -283,7 +292,12 @@
raise METALError("duplicate METAL attribute " +
`keybase`, self.getpos())
metaldict[keybase] = value
+ elif ns == 'i18n':
+ if i18ndict.has_key(keybase):
+ raise I18NError("duplicate i18n attribute " +
+ `keybase`, self.getpos())
+ i18ndict[keybase] = value
attrlist.append(item)
if namens in ('metal', 'tal'):
taldict['tal tag'] = namens
- return name, attrlist, taldict, metaldict
+ return name, attrlist, taldict, metaldict, i18ndict
=== Zope3/lib/python/Zope/TAL/TALGenerator.py 1.55 => 1.56 ===
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 = {}
self.slots = {}
@@ -102,9 +109,28 @@
else:
return item[0], tuple(item[1:])
- actionIndex = {"replace":0, "insert":1, "metal":2, "tal":3, "xmlns":4,
- 0: 0, 1: 1, 2: 2, 3: 3, 4: 4}
+ # 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
@@ -115,7 +141,6 @@
if len(item) > 2:
opt = 0
name, value, action = item[:3]
- action = self.actionIndex[action]
attrlist[i] = (name, value, action) + item[3:]
else:
if item[1] is None:
@@ -123,10 +148,8 @@
else:
s = "%s=%s" % (item[0], quote(item[1]))
attrlist[i] = item[0], s
- if item[1] is None:
- new.append(" " + item[0])
- else:
- new.append(" %s=%s" % (item[0], quote(item[1])))
+ new.append(" " + s)
+ # if no non-optimizable attributes were found, convert to plain text
if opt:
new.append(end)
collect.extend(new)
@@ -274,6 +297,40 @@
assert key == "structure"
self.emit("insertStructure", cexpr, attrDict, program)
+ def emitI18nVariable(self, varname, arg):
+ # Used for i18n:name attributes. arg is extra information describing
+ # how the contents of the variable should get filled in, and it will
+ # either be a 1-tuple or a 2-tuple. If arg[0] is None, then the
+ # i18n:name value is taken implicitly from the contents of the tag,
+ # e.g. "I live in <span i18n:name="country">the USA</span>". In this
+ # case, arg[1] is the opcode sub-program describing the contents of
+ # the tag.
+ #
+ # When arg[0] is not None, it contains the tal expression used to
+ # calculate the contents of the variable, e.g.
+ # "I live in <span i18n:name="country"
+ # tal:replace="here/countryOfOrigin" />"
+ key = cexpr = None
+ if arg[0] is not None:
+ key, expr = parseSubstitution(arg[0])
+ cexpr = self.compileExpression(expr)
+ else:
+ cexpr = self.optimize(arg[1][1:])
+ program = self.popProgram()
+ # XXX Would key be anything but 'text' or None?
+ assert key in ('text', None)
+ self.emit('i18nVariable', varname, cexpr, program)
+
+ 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()
@@ -361,23 +418,30 @@
return None
def replaceAttrs(self, attrlist, repldict):
+ # Each entry in attrlist starts like (name, value).
+ # Result is (name, value, action, expr, xlat) 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)
if not repldict:
return attrlist
newlist = []
for item in attrlist:
key = item[0]
if repldict.has_key(key):
- item = item[:2] + ("replace", repldict[key])
+ expr, xlat = repldict[key]
+ item = item[:2] + ("replace", expr, xlat)
del repldict[key]
newlist.append(item)
- for key, value in repldict.items(): # Add dynamic-only attributes
- item = (key, None, "insert", value)
- newlist.append(item)
+ # Add dynamic-only attributes
+ for key, (expr, xlat) in repldict.items():
+ newlist.append((key, None, "insert", expr, xlat))
return newlist
- def emitStartElement(self, name, attrlist, taldict, metaldict,
+ def emitStartElement(self, name, attrlist, taldict, metaldict, i18ndict,
position=(None, None), isend=0):
- if not taldict and not metaldict:
+ if not taldict and not metaldict and not i18ndict:
# Handle the simple, common case
self.emitStartTag(name, attrlist, isend)
self.todoPush({})
@@ -399,6 +463,12 @@
if not value:
raise TALError("missing value for METAL attribute: " +
`key`, position)
+ for key, value in i18ndict.items():
+ if key not in 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")
useMacro = metaldict.get("use-macro")
@@ -413,13 +483,31 @@
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 i18ndata and not msgid:
+ raise I18NError("i18n:data must be accompanied by i18n:translate",
+ position)
+
if len(metaldict) > 1 and (defineMacro or useMacro):
raise METALError("define-macro and use-macro cannot be used "
"together or with define-slot or fill-slot",
position)
- if content and replace:
- raise TALError("content and replace are mutually exclusive",
- position)
+ if replace:
+ if content:
+ raise TALError(
+ "tal:content and tal:replace are mutually exclusive",
+ position)
+ if msgid is not None:
+ raise I18NError(
+ "i18n:translate and tal:replace are mutually exclusive",
+ position)
repeatWhitespace = None
if repeat:
@@ -461,7 +549,13 @@
self.pushProgram()
todo["defineSlot"] = defineSlot
- if taldict:
+ if taldict or i18ndict:
+ if i18ndict:
+ if ( "domain" in i18ndict
+ or "source" in i18ndict
+ or "target" in i18ndict):
+ self.emit("beginI18nContext", i18ndict)
+ todo["i18ncontext"] = 1
dict = {}
for item in attrlist:
key, value = item[:2]
@@ -487,16 +581,43 @@
if content:
todo["content"] = content
if replace:
- todo["replace"] = replace
+ # tal:replace w/ i18n:name has slightly different semantics. What
+ # we're actually replacing then is the contents of the ${name}
+ # placeholder.
+ if varname:
+ todo['i18nvar'] = (varname, replace)
+ else:
+ todo["replace"] = replace
self.pushProgram()
+ # i18n:name w/o tal:replace uses the content as the interpolation
+ # dictionary values
+ elif varname:
+ todo['i18nvar'] = (varname, None)
+ self.pushProgram()
+ if msgid is not None:
+ 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:
- repldict = parseAttributeReplacements(attrsubst)
+ if attrsubst or i18nattrs:
+ if attrsubst:
+ repldict = parseAttributeReplacements(attrsubst)
+ else:
+ repldict = {}
+ if i18nattrs:
+ i18nattrs = i18nattrs.split()
+ else:
+ i18nattrs = ()
+ # Convert repldict's name-->expr mapping to a
+ # name-->(compiled_expr, translate) mapping
for key, value in repldict.items():
- repldict[key] = self.compileExpression(value)
+ repldict[key] = self.compileExpression(value), key in i18nattrs
+ for key in i18nattrs:
+ if key not in repldict:
+ repldict[key] = None, 1
else:
repldict = {}
if replace:
@@ -507,6 +628,8 @@
self.pushProgram()
if content:
self.pushProgram()
+ if msgid is not None:
+ self.pushProgram()
if todo and position != (None, None):
todo["position"] = position
self.todoPush(todo)
@@ -535,6 +658,10 @@
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:
@@ -548,12 +675,25 @@
if content:
self.emitSubstitution(content, {})
+ if msgid is not None:
+ self.emitTranslation(msgid, i18ndata)
if optTag:
self.emitOptTag(name, optTag, isend)
elif not isend:
+ # Before we emit the end tag, we need to see if the value of the
+ # current program is to be used as the value of the i18n:name
+ # interpolation variable. If so, we need to make a copy of the
+ # program /without the end tag/ and squirrel it away for later.
+ if not replace and varname and varname[1] is None:
+ varname += (self.program[:],)
self.emitEndTag(name)
+ # If i18n:name appeared in the same tag as tal:replace then we're
+ # going to do the substitution a little bit differently. The results
+ # of the expression go into the i18n substitution dictionary.
if replace:
self.emitSubstitution(replace, repldict)
+ elif varname:
+ self.emitI18nVariable(varname[0], varname[1:])
if repeat:
self.emitRepeat(repeat)
if condition:
@@ -562,6 +702,8 @@
self.emitOnError(name, onError)
if scope:
self.emit("endScope")
+ if i18ncontext:
+ self.emit("endI18nContext")
if defineSlot:
self.emitDefineSlot(defineSlot)
if fillSlot:
=== Zope3/lib/python/Zope/TAL/TALInterpreter.py 1.69 => 1.70 ===
import sys
import getopt
+import re
+from types import ListType
from cgi import escape
@@ -28,6 +30,7 @@
from TALDefs import quote, TAL_VERSION, TALError, METALError
from TALDefs import isCurrentVersion, getProgramVersion, getProgramMode
from TALGenerator import TALGenerator
+from TranslationContext import TranslationContext
BOOLEAN_HTML_ATTRS = [
# List of Boolean attributes in HTML that should be rendered in
@@ -62,14 +65,16 @@
if self.enabled:
apply(TALGenerator.emit, (self,) + args)
- def emitStartElement(self, name, attrlist, taldict, metaldict,
+ def emitStartElement(self, name, attrlist, taldict, metaldict, i18ndict,
position=(None, None), isend=0):
metaldict = {}
taldict = {}
+ i18ndict = {}
if self.enabled and self.repldict:
taldict["attributes"] = "x x"
TALGenerator.emitStartElement(self, name, attrlist,
- taldict, metaldict, position, isend)
+ taldict, metaldict, i18ndict,
+ position, isend)
def replaceAttrs(self, attrlist, repldict):
if self.enabled and self.repldict:
@@ -82,7 +87,7 @@
def __init__(self, program, macros, engine, stream=None,
debug=0, wrap=60, metal=1, tal=1, showtal=-1,
- strictinsert=1, stackLimit=100):
+ strictinsert=1, stackLimit=100, i18nInterpolate=1):
self.program = program
self.macros = macros
self.engine = engine # Execution engine (aka context)
@@ -113,22 +118,26 @@
self.level = 0
self.scopeLevel = 0
self.sourceFile = None
+ self.i18nStack = []
+ self.i18nInterpolate = i18nInterpolate
+ self.i18nContext = TranslationContext(domain="default")
def saveState(self):
return (self.position, self.col, self.stream,
- self.scopeLevel, self.level)
+ self.scopeLevel, self.level, self.i18nContext)
def restoreState(self, state):
- (self.position, self.col, self.stream, scopeLevel, level) = state
+ (self.position, self.col, self.stream, scopeLevel, level, i18n) = state
self._stream_write = self.stream.write
assert self.level == level
while self.scopeLevel > scopeLevel:
self.engine.endScope()
self.scopeLevel = self.scopeLevel - 1
self.engine.setPosition(self.position)
+ self.i18nContext = i18n
def restoreOutputState(self, state):
- (dummy, self.col, self.stream, scopeLevel, level) = state
+ (dummy, self.col, self.stream, scopeLevel, level, i18n) = state
self._stream_write = self.stream.write
assert self.level == level
assert self.scopeLevel == scopeLevel
@@ -151,9 +160,11 @@
def __call__(self):
assert self.level == 0
assert self.scopeLevel == 0
+ assert self.i18nContext.parent is None
self.interpret(self.program)
assert self.level == 0
assert self.scopeLevel == 0
+ assert self.i18nContext.parent is None
if self.col > 0:
self._stream_write("\n")
self.col = 0
@@ -169,10 +180,15 @@
bytecode_handlers = {}
- def interpret(self, program):
+ def interpret(self, program, tmpstream=None):
oldlevel = self.level
self.level = oldlevel + 1
handlers = self.dispatch
+ if tmpstream:
+ ostream = self.stream
+ owrite = self._stream_write
+ self.stream = mfp = tmpstream
+ self._stream_write = tmpstream.write
try:
if self.debug:
for (opcode, args) in program:
@@ -187,6 +203,9 @@
handlers[opcode](self, args)
finally:
self.level = oldlevel
+ if tmpstream:
+ self.stream = ostream
+ self._stream_write = owrite
def do_version(self, version):
assert version == TAL_VERSION
@@ -257,10 +276,11 @@
def attrAction(self, item):
name, value, action = item[:3]
- if action == 1 or (action > 1 and not self.showtal):
+ if action == 'insert' or (action in ('metal', 'tal', 'xmlns', 'i18n')
+ and not self.showtal):
return 0, name, value
macs = self.macroStack
- if action == 2 and self.metal and macs:
+ if action == 'metal' and self.metal and macs:
if len(macs) > 1 or not macs[-1][2]:
# Drop all METAL attributes at a use-depth above one.
return 0, name, value
@@ -288,33 +308,42 @@
def attrAction_tal(self, item):
name, value, action = item[:3]
- if action > 1:
+ if action in ('metal', 'tal', 'xmlns', 'i18n'):
return self.attrAction(item)
ok = 1
+ expr, msgid = item[3:]
if self.html and name.lower() in BOOLEAN_HTML_ATTRS:
evalue = self.engine.evaluateBoolean(item[3])
if evalue is self.Default:
- if action == 1: # Cancelled insert
+ if action == 'insert': # Cancelled insert
ok = 0
elif evalue:
value = None
else:
ok = 0
else:
- evalue = self.engine.evaluateText(item[3])
- if evalue is self.Default:
- if action == 1: # Cancelled insert
- ok = 0
- else:
- if evalue is None:
- ok = 0
- value = evalue
+ if expr is not None:
+ evalue = self.engine.evaluateText(item[3])
+ if evalue is self.Default:
+ if action == 'insert': # Cancelled insert
+ ok = 0
+ else:
+ if evalue is None:
+ ok = 0
+ value = evalue
if ok:
+ if msgid:
+ value = self.i18n_attribute(value)
if value is None:
value = name
value = "%s=%s" % (name, quote(value))
return ok, name, value
+ def i18n_attribute(self, s):
+ # s is the value of an attribute before translation
+ # it may have been computed
+ raise NotImplementedError("cannot translate %r" % s)
+
bytecode_handlers["<attrAction>"] = attrAction
def no_tag(self, start, program):
@@ -407,6 +436,19 @@
self.engine.setGlobal(name, self.engine.evaluateValue(expr))
bytecode_handlers["setGlobal"] = do_setLocal
+ def do_beginI18nContext(self, settings):
+ get = settings.get
+ self.i18nContext = TranslationContext(self.i18nContext,
+ domain=get("domain"),
+ source=get("source"),
+ target=get("target"))
+ bytecode_handlers["beginI18nContext"] = do_beginI18nContext
+
+ def do_endI18nContext(self):
+ self.i18nContext = self.i18nContext.parent
+ assert self.i18nContext is not None
+ bytecode_handlers["endI18nContext"] = do_endI18nContext
+
def do_insertText(self, stuff):
self.interpret(stuff[1])
@@ -426,6 +468,68 @@
self.col = len(s) - (i + 1)
bytecode_handlers["insertText"] = do_insertText
+ def do_i18nVariable(self, stuff):
+ varname = stuff[0]
+ if isinstance(stuff[1], ListType):
+ # The value is implicitly the contents of this tag, so we have to
+ # evaluate the mini-program
+ state = self.saveState()
+ try:
+ tmpstream = StringIO()
+ self.interpret(stuff[1], tmpstream)
+ value = tmpstream.getvalue()
+ finally:
+ self.restoreState(state)
+ else:
+ # Evaluate the value to be associated with the variable in the
+ # i18n interpolation dictionary.
+ value = self.engine.evaluate(stuff[1])
+ # Either the i18n:name tag is nested inside an i18n:translate in which
+ # case the last item on the stack has the i18n dictionary and string
+ # representation, or the i18n:name and i18n:translate attributes are
+ # in the same tag, in which case the i18nStack will be empty. In that
+ # case we can just output the ${name} to the stream
+ i18ndict, srepr = self.i18nStack[-1]
+ i18ndict[varname] = value
+ placeholder = '${%s}' % varname
+ srepr.append(placeholder)
+ self._stream_write(placeholder)
+ bytecode_handlers['i18nVariable'] = do_i18nVariable
+
+ def do_insertTranslation(self, stuff):
+ i18ndict = {}
+ srepr = []
+ obj = None
+ self.i18nStack.append((i18ndict, srepr))
+ msgid = stuff[0]
+ if msgid == '':
+ # The content is the implicit message id. Use a temporary stream
+ # to capture the interpretation of the subnodes, which should
+ # /not/ go to the output stream.
+ tmpstream = StringIO()
+ self.interpret(stuff[1], tmpstream)
+ msgid = tmpstream.getvalue()
+ # Now we need to normalize the whitespace in the implicit message
+ # id by stripping leading and trailing whitespace, and folding all
+ # internal whitespace to a single space.
+ msgid = ' '.join(msgid.split())
+ self.i18nStack.pop()
+ # See if there is was an i18n:data for msgid
+ if len(stuff) > 2:
+ obj = self.engine.evaluate(stuff[2])
+ xlated_msgid = self.translate(msgid, i18ndict, obj)
+ # XXX I can't decide whether we want to cgi escape the translated
+ # string or not. OT1H not doing this could introduce a cross-site
+ # scripting vector by allowing translators to sneak JavaScript into
+ # translations. OTOH, for implicit interpolation values, we don't
+ # want to escape stuff like ${name} <= "<b>Timmy</b>".
+ #s = escape(xlated_msgid)
+ s = xlated_msgid
+ # If there are i18n variables to interpolate into this string, better
+ # do it now.
+ self._stream_write(s)
+ bytecode_handlers['insertTranslation'] = do_insertTranslation
+
def do_insertStructure(self, stuff):
self.interpret(stuff[2])
@@ -447,16 +551,6 @@
self.insertXMLStructure(text, repldict)
bytecode_handlers["insertStructure"] = do_insertStructure
-
-# XXX There is a bug in the dance between TALInterpreter and TALES.
-# TALInterpreter expects contexts to also be engines. When someone
-# inserts structure, the structure can, apparently have TAL, because
-# the TAL compiler is used. If there was TAL, it would try to use the
-# engine, which is a context, to compile expressions found. The TALES
-# context is not a compiler. Is this a YAGNI?
-
-
-
def insertHTMLStructure(self, text, repldict):
from HTMLTALParser import HTMLTALParser
gen = AltTALGenerator(repldict, self.engine, 0)
@@ -486,6 +580,19 @@
while iterator.next():
self.interpret(block)
bytecode_handlers["loop"] = do_loop
+
+ def translate(self, msgid, i18ndict=None, obj=None):
+ # XXX is this right?
+ if obj:
+ i18ndict.update(obj)
+ # XXX need to fill this in with TranslationService calls. For now,
+ # we'll just do simple interpolation based on a $-strings to %-strings
+ # algorithm in Mailman.
+ if not self.i18nInterpolate:
+ return msgid
+ service = self.engine.getTranslationService()
+ # We need to pass in one of context or target_language
+ return service.translate(self.i18nContext.domain, msgid, i18ndict)
def do_rawtextColumn(self, (s, col)):
self._stream_write(s)
=== Zope3/lib/python/Zope/TAL/TALParser.py 1.19 => 1.20 ===
attrlist = attrs.items()
attrlist.sort() # For definiteness
- name, attrlist, taldict, metaldict = self.process_ns(name, attrlist)
+ name, attrlist, taldict, metaldict, i18ndict \
+ = self.process_ns(name, attrlist)
attrlist = self.xmlnsattrs() + attrlist
- self.gen.emitStartElement(name, attrlist, taldict, metaldict)
+ self.gen.emitStartElement(name, attrlist, taldict, metaldict, i18ndict)
def process_ns(self, name, attrlist):
taldict = {}
metaldict = {}
+ i18ndict = {}
fixedattrlist = []
name, namebase, namens = self.fixname(name)
for key, value in attrlist:
@@ -77,10 +79,14 @@
elif ns == 'tal':
taldict[keybase] = value
item = item + ("tal",)
+ elif ns == 'i18n':
+ assert 0, "dealing with i18n: " + `(keybase, value)`
+ i18ndict[keybase] = value
+ item = item + ('i18n',)
fixedattrlist.append(item)
- if namens in ('metal', 'tal'):
+ if namens in ('metal', 'tal', 'i18n'):
taldict['tal tag'] = namens
- return name, fixedattrlist, taldict, metaldict
+ return name, fixedattrlist, taldict, metaldict, i18ndict
def xmlnsattrs(self):
newlist = []
@@ -89,7 +95,7 @@
key = "xmlns:" + prefix
else:
key = "xmlns"
- if uri in (ZOPE_METAL_NS, ZOPE_TAL_NS):
+ if uri in (ZOPE_METAL_NS, ZOPE_TAL_NS, ZOPE_I18N_NS):
item = (key, uri, "xmlns")
else:
item = (key, uri)
@@ -109,6 +115,8 @@
ns = 'tal'
elif uri == ZOPE_METAL_NS:
ns = 'metal'
+ elif uri == ZOPE_I18N_NS:
+ ns = 'i18n'
return (prefixed, name, ns)
return (name, name, None)