[Zope-Checkins] CVS: Zope/lib/python/TAL - DummyEngine.py:1.37 TALDefs.py:1.36 TALGenerator.py:1.63 TALInterpreter.py:1.78 driver.py:1.30
Fred L. Drake, Jr.
fred@zope.com
Mon, 7 Apr 2003 13:38:58 -0400
Update of /cvs-repository/Zope/lib/python/TAL
In directory cvs.zope.org:/tmp/cvs-serv18558/TAL
Modified Files:
DummyEngine.py TALDefs.py TALGenerator.py TALInterpreter.py
driver.py
Log Message:
Back-port two features from the Zope 3 version of Page Templates:
- avoid normalizing whitespace when using the default text when there is not
a matching translation
- added support for explicit msgids in the i18n:attributes syntax
=== Zope/lib/python/TAL/DummyEngine.py 1.36 => 1.37 ===
--- Zope/lib/python/TAL/DummyEngine.py:1.36 Thu Jan 30 13:18:43 2003
+++ Zope/lib/python/TAL/DummyEngine.py Mon Apr 7 13:38:27 2003
@@ -206,12 +206,18 @@
def getDefault(self):
return Default
- def translate(self, domain, msgid, mapping):
- return self.translationService.translate(domain, msgid, mapping)
-
+ def translate(self, domain, msgid, mapping, default=None):
+ return self.translationService.translate(domain, msgid, mapping,
+ default=default)
+
class Iterator:
+ # This is not an implementation of a Python iterator. The next()
+ # method returns true or false to indicate whether another item is
+ # available; if there is another item, the iterator instance calls
+ # setLocal() on the evaluation engine passed to the constructor.
+
def __init__(self, name, seq, engine):
self.name = name
self.seq = seq
@@ -232,26 +238,33 @@
__implements__ = IDomain
def translate(self, msgid, mapping=None, context=None,
- target_language=None):
+ target_language=None, default=None):
# This is a fake translation service which simply uppercases non
# ${name} placeholder text in the message id.
#
# First, transform a string with ${name} placeholders into a list of
# substrings. Then upcase everything but the placeholders, then glue
# things back together.
+
+ # simulate an unknown msgid by returning None
+ if msgid == "don't translate me":
+ text = default
+ else:
+ text = msgid.upper()
+
def repl(m, mapping=mapping):
return ustr(mapping[m.group(m.lastindex).lower()])
- cre = re.compile(r'\$(?:([_A-Z]\w*)|\{([_A-Z]\w*)\})')
- return cre.sub(repl, msgid.upper())
+ cre = re.compile(r'\$(?:(%s)|\{(%s)\})' % (NAME_RE, NAME_RE))
+ return cre.sub(repl, text)
class DummyTranslationService:
__implements__ = ITranslationService
def translate(self, domain, msgid, mapping=None, context=None,
- target_language=None):
- # Ignore domain
+ target_language=None, default=None):
return self.getDomain(domain).translate(msgid, mapping, context,
- target_language)
+ target_language,
+ default=default)
def getDomain(self, domain):
return DummyDomain()
=== Zope/lib/python/TAL/TALDefs.py 1.35 => 1.36 ===
--- Zope/lib/python/TAL/TALDefs.py:1.35 Thu Mar 20 14:58:27 2003
+++ Zope/lib/python/TAL/TALDefs.py Mon Apr 7 13:38:27 2003
@@ -28,7 +28,9 @@
ZOPE_METAL_NS = "http://xml.zope.org/namespaces/metal"
ZOPE_I18N_NS = "http://xml.zope.org/namespaces/i18n"
-NAME_RE = "[a-zA-Z_][a-zA-Z0-9_]*"
+# This RE must exactly match the expression of the same name in the
+# zope.i18n.simpletranslationservice module:
+NAME_RE = "[a-zA-Z_][-a-zA-Z0-9_]*"
KNOWN_METAL_ATTRIBUTES = [
"define-macro",
@@ -118,12 +120,12 @@
for part in splitParts(arg):
m = _attr_re.match(part)
if not m:
- raise TALError("Bad syntax in attributes:" + `part`)
+ raise TALError("Bad syntax in attributes: " + `part`)
name, expr = m.group(1, 2)
if not xml:
name = name.lower()
if dict.has_key(name):
- raise TALError("Duplicate attribute name in attributes:" + `part`)
+ raise TALError("Duplicate attribute name in attributes: " + `part`)
dict[name] = expr
return dict
=== Zope/lib/python/TAL/TALGenerator.py 1.62 => 1.63 ===
--- Zope/lib/python/TAL/TALGenerator.py:1.62 Thu Mar 20 14:58:27 2003
+++ Zope/lib/python/TAL/TALGenerator.py Mon Apr 7 13:38:27 2003
@@ -29,6 +29,9 @@
I18N_CONTENT = 2
I18N_EXPRESSION = 3
+_name_rx = re.compile(NAME_RE)
+
+
class TALGenerator:
inMacroUse = 0
@@ -329,6 +332,9 @@
# calculate the contents of the variable, e.g.
# "I live in <span i18n:name="country"
# tal:replace="here/countryOfOrigin" />"
+ m = _name_rx.match(varname)
+ if m is None or m.group() != varname:
+ raise TALError("illegal i18n:name: %r" % varname, self.position)
key = cexpr = None
program = self.popProgram()
if action == I18N_REPLACE:
@@ -458,13 +464,13 @@
for item in attrlist:
key = item[0]
if repldict.has_key(key):
- expr, xlat = repldict[key]
- item = item[:2] + ("replace", expr, xlat)
+ 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) in repldict.items():
- newlist.append((key, None, "insert", expr, xlat))
+ 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,
@@ -651,16 +657,18 @@
else:
repldict = {}
if i18nattrs:
- i18nattrs = i18nattrs.split()
+ i18nattrs = _parseI18nAttributes(i18nattrs, self.position,
+ self.xml)
else:
- i18nattrs = ()
+ 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), key in i18nattrs
+ ce = self.compileExpression(value)
+ repldict[key] = ce, key in i18nattrs, i18nattrs.get(key)
for key in i18nattrs:
if not repldict.has_key(key):
- repldict[key] = None, 1
+ repldict[key] = None, 1, i18nattrs.get(key)
else:
repldict = {}
if replace:
@@ -781,6 +789,30 @@
self.emitUseMacro(useMacro)
if defineMacro:
self.emitDefineMacro(defineMacro)
+
+
+def _parseI18nAttributes(i18nattrs, position, xml):
+ d = {}
+ for spec in i18nattrs.split(";"):
+ parts = spec.split()
+ if len(parts) > 2:
+ raise TALError("illegal i18n:attributes specification: %r" % spec,
+ position)
+ if len(parts) == 2:
+ attr, msgid = parts
+ else:
+ # len(parts) == 1
+ attr = parts[0]
+ msgid = None
+ 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()
=== Zope/lib/python/TAL/TALInterpreter.py 1.77 => 1.78 ===
--- Zope/lib/python/TAL/TALInterpreter.py:1.77 Thu Feb 27 11:18:40 2003
+++ Zope/lib/python/TAL/TALInterpreter.py Mon Apr 7 13:38:27 2003
@@ -349,7 +349,7 @@
return self.attrAction(item)
name, value, action = item[:3]
ok = 1
- expr, msgid = item[3:]
+ expr, xlat, msgid = item[3:]
if self.html and name.lower() in BOOLEAN_HTML_ATTRS:
evalue = self.engine.evaluateBoolean(item[3])
if evalue is self.Default:
@@ -368,23 +368,18 @@
if evalue is None:
ok = 0
value = evalue
- if msgid:
- value = self.i18n_attribute(value)
- if value is None:
- value = name
- value = '%s="%s"' % (name, escape(value, 1))
+
+ if ok:
+ if xlat:
+ translated = self.translate(msgid or value, value, {})
+ if translated is not None:
+ value = translated
+ if value is None:
+ value = name
+ value = '%s="%s"' % (name, escape(value, 1))
return ok, name, value
bytecode_handlers["<attrAction>"] = attrAction
- def i18n_attribute(self, s):
- # s is the value of an attribute before translation
- # it may have been computed
- xlated = self.translate(s, {})
- if xlated is None:
- return s
- else:
- return xlated
-
def no_tag(self, start, program):
state = self.saveState()
self.stream = stream = self.StringIO()
@@ -553,36 +548,19 @@
# subnodes, which should /not/ go to the output stream.
tmpstream = self.StringIO()
self.interpretWithStream(stuff[1], tmpstream)
- content = None
+ default = tmpstream.getvalue()
# We only care about the evaluated contents if we need an implicit
# message id. All other useful information will be in the i18ndict on
# the top of the i18nStack.
if msgid == '':
- content = tmpstream.getvalue()
- msgid = normalize(content)
+ msgid = normalize(default)
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)
- # If there is no translation available, use evaluated content.
- if xlated_msgid is None:
- if content is None:
- content = tmpstream.getvalue()
- # We must do potential substitutions "by hand".
- s = interpolate(content, i18ndict)
- else:
- # 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.
- # XXX efge: actually, this is already done by the translation service.
- self._stream_write(s)
+ xlated_msgid = self.translate(msgid, default, i18ndict, obj)
+ assert xlated_msgid is not None, self.position
+ self._stream_write(xlated_msgid)
bytecode_handlers['insertTranslation'] = do_insertTranslation
def do_insertStructure(self, stuff):
@@ -636,23 +614,14 @@
self.interpret(block)
bytecode_handlers["loop"] = do_loop
- def translate(self, msgid, i18ndict=None, obj=None):
- # XXX is this right?
- if i18ndict is None:
- i18ndict = {}
+ def translate(self, msgid, default, i18ndict, obj=None):
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
- # XXX Mmmh, it seems that sometimes the msgid is None; is that really
- # possible?
- if msgid is None:
- return None
# XXX We need to pass in one of context or target_language
- return self.engine.translate(self.i18nContext.domain, msgid, i18ndict)
+ return self.engine.translate(self.i18nContext.domain,
+ msgid, i18ndict, default=default)
def do_rawtextColumn(self, (s, col)):
self._stream_write(s)
=== Zope/lib/python/TAL/driver.py 1.29 => 1.30 ===
--- Zope/lib/python/TAL/driver.py:1.29 Wed Sep 18 11:12:48 2002
+++ Zope/lib/python/TAL/driver.py Mon Apr 7 13:38:27 2003
@@ -53,7 +53,7 @@
class TestTranslations(DummyTranslationService):
def translate(self, domain, msgid, mapping=None, context=None,
- target_language=None):
+ target_language=None, default=None):
if msgid == 'timefmt':
return '%(minutes)s minutes after %(hours)s %(ampm)s' % mapping
elif msgid == 'jobnum':
@@ -67,7 +67,8 @@
return '%(name)s was born in %(country)s' % mapping
return DummyTranslationService.translate(self, domain, msgid,
mapping, context,
- target_language)
+ target_language,
+ default=default)
class TestEngine(DummyEngine):
def __init__(self, macros=None):