Commit 702c4880 authored by 's avatar

merged yuppie-tal-backports branch:

- made sure custom strings like MessageID are not converted by ustr
- synced TALInterpreter code and tests with zope.tal where possible without changing behavior
- backported MessageID support from zope.tal
parent 1bd27dd7
...@@ -26,6 +26,11 @@ Zope Changes ...@@ -26,6 +26,11 @@ Zope Changes
Bugs Fixed Bugs Fixed
- TAL: MassageIDs are now handled the same way as in zope.tal.
- DocumentTemplate: ustr no longer mangles MassageIDs.
Custom string types are now returned unchanged.
- As developed in a long thread starting at - As developed in a long thread starting at
http://mail.zope.org/pipermail/zope/2005-July/160433.html http://mail.zope.org/pipermail/zope/2005-July/160433.html
there appears to be a race bug in the Microsoft Windows socket there appears to be a race bug in the Microsoft Windows socket
......
...@@ -7,20 +7,18 @@ ...@@ -7,20 +7,18 @@
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE # FOR A PARTICULAR PURPOSE.
# #
############################################################################## ##############################################################################
"""Document Template Tests """ustr unit tests.
"""
__rcs_id__='$Id$' $Id$
__version__='$Revision: 1.3 $'[11:-2] """
import sys, os
import unittest import unittest
from DocumentTemplate.ustr import ustr from DocumentTemplate.ustr import ustr
from ExtensionClass import Base
class force_str: class force_str:
# A class whose string representation is not always a plain string: # A class whose string representation is not always a plain string:
...@@ -29,7 +27,18 @@ class force_str: ...@@ -29,7 +27,18 @@ class force_str:
def __str__(self): def __str__(self):
return self.s return self.s
class UnicodeTests (unittest.TestCase):
class Foo(str):
pass
class Bar(unicode):
pass
class UnicodeTests(unittest.TestCase):
def testPlain(self): def testPlain(self):
a = ustr('hello') a = ustr('hello')
...@@ -59,13 +68,17 @@ class UnicodeTests (unittest.TestCase): ...@@ -59,13 +68,17 @@ class UnicodeTests (unittest.TestCase):
a = ustr(ValueError(unichr(200))) a = ustr(ValueError(unichr(200)))
assert a==unichr(200), `a` assert a==unichr(200), `a`
def testCustomStrings(self):
a = ustr(Foo('foo'))
self.failUnlessEqual(type(a), Foo)
a = ustr(Bar('bar'))
self.failUnlessEqual(type(a), Bar)
def test_suite(): def test_suite():
suite = unittest.TestSuite() suite = unittest.TestSuite()
suite.addTest( unittest.makeSuite( UnicodeTests ) ) suite.addTest( unittest.makeSuite( UnicodeTests ) )
return suite return suite
def main():
unittest.TextTestRunner().run(test_suite())
if __name__ == '__main__': if __name__ == '__main__':
main() unittest.main(defaultTest='test_suite')
...@@ -7,11 +7,13 @@ ...@@ -7,11 +7,13 @@
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE # FOR A PARTICULAR PURPOSE.
# #
############################################################################## ##############################################################################
"""ustr function.
from types import StringType, UnicodeType, InstanceType $Id$
"""
nasty_exception_str = Exception.__str__.im_func nasty_exception_str = Exception.__str__.im_func
...@@ -20,8 +22,7 @@ def ustr(v): ...@@ -20,8 +22,7 @@ def ustr(v):
minimising the chance of raising a UnicodeError. This minimising the chance of raising a UnicodeError. This
even works with uncooperative objects like Exceptions even works with uncooperative objects like Exceptions
""" """
string_types = (StringType,UnicodeType) if isinstance(v, basestring):
if type(v) in string_types:
return v return v
else: else:
fn = getattr(v,'__str__',None) fn = getattr(v,'__str__',None)
...@@ -41,7 +42,7 @@ def ustr(v): ...@@ -41,7 +42,7 @@ def ustr(v):
else: else:
# Trust the object to do this right # Trust the object to do this right
v = fn() v = fn()
if type(v) in string_types: if isinstance(v, basestring):
return v return v
else: else:
raise ValueError('__str__ returned wrong type') raise ValueError('__str__ returned wrong type')
...@@ -59,4 +60,3 @@ def _exception_str(exc): ...@@ -59,4 +60,3 @@ def _exception_str(exc):
else: else:
return str(exc.args) return str(exc.args)
return str(exc) return str(exc)
...@@ -4,29 +4,32 @@ ...@@ -4,29 +4,32 @@
# All Rights Reserved. # All Rights Reserved.
# #
# This software is subject to the provisions of the Zope Public License, # This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # 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 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE. # FOR A PARTICULAR PURPOSE.
# #
############################################################################## ##############################################################################
""" """Interpreter for a pre-compiled TAL program.
Interpreter for a pre-compiled TAL program.
$Id$
""" """
import cgi import cgi
import sys import sys
import getopt import getopt
import re import re
from types import ListType
from cgi import escape from cgi import escape
# Do not use cStringIO here! It's not unicode aware. :( # Do not use cStringIO here! It's not unicode aware. :(
from StringIO import StringIO from StringIO import StringIO
from DocumentTemplate.DT_Util import ustr from DocumentTemplate.DT_Util import ustr
from ZODB.POSException import ConflictError from ZODB.POSException import ConflictError
from TALDefs import TAL_VERSION, TALError, METALError, attrEscape from zope.i18nmessageid import MessageID
from TALDefs import isCurrentVersion, getProgramVersion, getProgramMode from TALDefs import attrEscape, TAL_VERSION, METALError
from TALDefs import isCurrentVersion
from TALDefs import getProgramVersion, getProgramMode
from TALGenerator import TALGenerator from TALGenerator import TALGenerator
from TranslationContext import TranslationContext from TranslationContext import TranslationContext
...@@ -41,11 +44,22 @@ BOOLEAN_HTML_ATTRS = [ ...@@ -41,11 +44,22 @@ BOOLEAN_HTML_ATTRS = [
"defer" "defer"
] ]
def _init():
d = {}
for s in BOOLEAN_HTML_ATTRS:
d[s] = 1
return d
BOOLEAN_HTML_ATTRS = _init()
_nulljoin = ''.join
_spacejoin = ' '.join
def normalize(text): def normalize(text):
# Now we need to normalize the whitespace in implicit message ids and # Now we need to normalize the whitespace in implicit message ids and
# implicit $name substitution values by stripping leading and trailing # implicit $name substitution values by stripping leading and trailing
# whitespace, and folding all internal whitespace to a single space. # whitespace, and folding all internal whitespace to a single space.
return ' '.join(text.split()) return _spacejoin(text.split())
NAME_RE = r"[a-zA-Z][a-zA-Z0-9_]*" NAME_RE = r"[a-zA-Z][a-zA-Z0-9_]*"
...@@ -159,7 +173,8 @@ class TALInterpreter: ...@@ -159,7 +173,8 @@ class TALInterpreter:
self.scopeLevel, self.level, self.i18nContext) self.scopeLevel, self.level, self.i18nContext)
def restoreState(self, state): def restoreState(self, state):
(self.position, self.col, self.stream, scopeLevel, level, i18n) = state (self.position, self.col, self.stream,
scopeLevel, level, i18n) = state
self._stream_write = self.stream.write self._stream_write = self.stream.write
assert self.level == level assert self.level == level
while self.scopeLevel > scopeLevel: while self.scopeLevel > scopeLevel:
...@@ -169,7 +184,8 @@ class TALInterpreter: ...@@ -169,7 +184,8 @@ class TALInterpreter:
self.i18nContext = i18n self.i18nContext = i18n
def restoreOutputState(self, state): def restoreOutputState(self, state):
(dummy, self.col, self.stream, scopeLevel, level, i18n) = state (dummy, self.col, self.stream,
scopeLevel, level, i18n) = state
self._stream_write = self.stream.write self._stream_write = self.stream.write
assert self.level == level assert self.level == level
assert self.scopeLevel == scopeLevel assert self.scopeLevel == scopeLevel
...@@ -195,6 +211,16 @@ class TALInterpreter: ...@@ -195,6 +211,16 @@ class TALInterpreter:
self._stream_write("\n") self._stream_write("\n")
self.col = 0 self.col = 0
def interpretWithStream(self, program, stream):
oldstream = self.stream
self.stream = stream
self._stream_write = stream.write
try:
self.interpret(program)
finally:
self.stream = oldstream
self._stream_write = oldstream.write
def stream_write(self, s, def stream_write(self, s,
len=len): len=len):
self._stream_write(s) self._stream_write(s)
...@@ -206,16 +232,6 @@ class TALInterpreter: ...@@ -206,16 +232,6 @@ class TALInterpreter:
bytecode_handlers = {} bytecode_handlers = {}
def interpretWithStream(self, program, stream):
oldstream = self.stream
self.stream = stream
self._stream_write = stream.write
try:
self.interpret(program)
finally:
self.stream = oldstream
self._stream_write = oldstream.write
def interpret(self, program): def interpret(self, program):
oldlevel = self.level oldlevel = self.level
self.level = oldlevel + 1 self.level = oldlevel + 1
...@@ -269,6 +285,7 @@ class TALInterpreter: ...@@ -269,6 +285,7 @@ class TALInterpreter:
# for start tags with no attributes; those are optimized down # for start tags with no attributes; those are optimized down
# to rawtext events. Hence, there is no special "fast path" # to rawtext events. Hence, there is no special "fast path"
# for that case. # for that case.
self._currentTag = name
L = ["<", name] L = ["<", name]
append = L.append append = L.append
col = self.col + _len(name) + 1 col = self.col + _len(name) + 1
...@@ -303,9 +320,9 @@ class TALInterpreter: ...@@ -303,9 +320,9 @@ class TALInterpreter:
col = col + 1 + slen col = col + 1 + slen
append(s) append(s)
append(end) append(end)
self._stream_write("".join(L))
col = col + endlen col = col + endlen
finally: finally:
self._stream_write(_nulljoin(L))
self.col = col self.col = col
bytecode_handlers["startTag"] = do_startTag bytecode_handlers["startTag"] = do_startTag
...@@ -487,6 +504,9 @@ class TALInterpreter: ...@@ -487,6 +504,9 @@ class TALInterpreter:
if text is self.Default: if text is self.Default:
self.interpret(stuff[1]) self.interpret(stuff[1])
return return
if isinstance(text, MessageID):
# Translate this now.
text = self.engine.translate(text.domain, text, text.mapping)
s = escape(text) s = escape(text)
self._stream_write(s) self._stream_write(s)
i = s.rfind('\n') i = s.rfind('\n')
...@@ -505,6 +525,9 @@ class TALInterpreter: ...@@ -505,6 +525,9 @@ class TALInterpreter:
try: try:
tmpstream = self.StringIO() tmpstream = self.StringIO()
self.interpretWithStream(program, tmpstream) self.interpretWithStream(program, tmpstream)
if self.html and self._currentTag == "pre":
value = tmpstream.getvalue()
else:
value = normalize(tmpstream.getvalue()) value = normalize(tmpstream.getvalue())
finally: finally:
self.restoreState(state) self.restoreState(state)
...@@ -516,8 +539,14 @@ class TALInterpreter: ...@@ -516,8 +539,14 @@ class TALInterpreter:
else: else:
value = self.engine.evaluate(expression) value = self.engine.evaluate(expression)
# evaluate() does not do any I18n, so we do it here.
if isinstance(value, MessageID):
# Translate this now.
value = self.engine.translate(value.domain, value,
value.mapping)
if not structure: if not structure:
value = cgi.escape(str(value)) value = cgi.escape(ustr(value))
# Either the i18n:name tag is nested inside an i18n:translate in which # 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 # case the last item on the stack has the i18n dictionary and string
...@@ -545,20 +574,29 @@ class TALInterpreter: ...@@ -545,20 +574,29 @@ class TALInterpreter:
# #
# Use a temporary stream to capture the interpretation of the # Use a temporary stream to capture the interpretation of the
# subnodes, which should /not/ go to the output stream. # subnodes, which should /not/ go to the output stream.
currentTag = self._currentTag
tmpstream = self.StringIO() tmpstream = self.StringIO()
self.interpretWithStream(stuff[1], tmpstream) self.interpretWithStream(stuff[1], tmpstream)
default = tmpstream.getvalue()
# We only care about the evaluated contents if we need an implicit # We only care about the evaluated contents if we need an implicit
# message id. All other useful information will be in the i18ndict on # message id. All other useful information will be in the i18ndict on
# the top of the i18nStack. # the top of the i18nStack.
if msgid == '': default = tmpstream.getvalue()
if not msgid:
if self.html and currentTag == "pre":
msgid = default
else:
msgid = normalize(default) msgid = normalize(default)
self.i18nStack.pop() self.i18nStack.pop()
# See if there is was an i18n:data for msgid # See if there is was an i18n:data for msgid
if len(stuff) > 2: if len(stuff) > 2:
obj = self.engine.evaluate(stuff[2]) obj = self.engine.evaluate(stuff[2])
xlated_msgid = self.translate(msgid, default, i18ndict, obj) xlated_msgid = self.translate(msgid, default, i18ndict, obj)
assert xlated_msgid is not None, self.position # 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>".
assert xlated_msgid is not None
self._stream_write(xlated_msgid) self._stream_write(xlated_msgid)
bytecode_handlers['insertTranslation'] = do_insertTranslation bytecode_handlers['insertTranslation'] = do_insertTranslation
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment