Commit f97d3008 authored by Florent Guillaume's avatar Florent Guillaume

Merge of the Zope-2_6-i18n-branch into HEAD.

Impacted code:
- TAL: merge of the 2.7 i18n stuff, unicode fixes, tests.
- PageTemplates: addition of a global translation service and of its use
  by the TALES engine, unicode fixes, tests.
- StructuredText: unicode fixes, tests.
parent 66478f12
...@@ -17,7 +17,7 @@ Page Template-specific implementation of TALES, with handlers ...@@ -17,7 +17,7 @@ Page Template-specific implementation of TALES, with handlers
for Python expressions, string literals, and paths. for Python expressions, string literals, and paths.
""" """
__version__='$Revision: 1.37 $'[11:-2] __version__='$Revision: 1.38 $'[11:-2]
import re, sys import re, sys
from TALES import Engine, CompilerError, _valid_name, NAME_RE, \ from TALES import Engine, CompilerError, _valid_name, NAME_RE, \
...@@ -246,7 +246,10 @@ class NotExpr: ...@@ -246,7 +246,10 @@ class NotExpr:
self._c = compiler.compile(expr) self._c = compiler.compile(expr)
def __call__(self, econtext): def __call__(self, econtext):
return not econtext.evaluateBoolean(self._c) # We use the (not x) and 1 or 0 formulation to avoid changing
# the representation of the result in Python 2.3, where the
# result of "not" becomes an instance of bool.
return (not econtext.evaluateBoolean(self._c)) and 1 or 0
def __repr__(self): def __repr__(self):
return 'not:%s' % `self._s` return 'not:%s' % `self._s`
......
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# 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.
# 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.
#
##############################################################################
"""Global Translation Service for providing I18n to Page Templates.
$Id: GlobalTranslationService.py,v 1.2 2002/09/18 15:12:46 efge Exp $
"""
class DummyTranslationService:
"""Translation service that does nothing and returns the message id."""
def translate(self, domain, msgid, mapping=None,
context=None, target_language=None):
return msgid
# XXX Not all of Zope.I18n.ITranslationService is implemented.
translationService = DummyTranslationService()
def setGlobalTranslationService(service):
"""Sets the global translation service, and returns the previous one."""
global translationService
old_service = translationService
translationService = service
return old_service
def getGlobalTranslationService():
"""Returns the global translation service."""
return translationService
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
HTML- and XML-based template objects using TAL, TALES, and METAL. HTML- and XML-based template objects using TAL, TALES, and METAL.
""" """
__version__='$Revision: 1.27 $'[11:-2] __version__='$Revision: 1.28 $'[11:-2]
import sys import sys
...@@ -24,7 +24,8 @@ from TAL.HTMLTALParser import HTMLTALParser ...@@ -24,7 +24,8 @@ from TAL.HTMLTALParser import HTMLTALParser
from TAL.TALGenerator import TALGenerator from TAL.TALGenerator import TALGenerator
from TAL.TALInterpreter import TALInterpreter from TAL.TALInterpreter import TALInterpreter
from Expressions import getEngine from Expressions import getEngine
from cStringIO import StringIO # Do not use cStringIO here! It's not unicode aware. :(
from StringIO import StringIO
from ExtensionClass import Base from ExtensionClass import Base
from ComputedAttribute import ComputedAttribute from ComputedAttribute import ComputedAttribute
...@@ -208,3 +209,4 @@ class PageTemplateTracebackSupplement: ...@@ -208,3 +209,4 @@ class PageTemplateTracebackSupplement:
if e: if e:
w = list(w) + list(e) w = list(w) + list(e)
self.warnings = w self.warnings = w
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
Zope object encapsulating a Page Template from the filesystem. Zope object encapsulating a Page Template from the filesystem.
""" """
__version__='$Revision: 1.20 $'[11:-2] __version__='$Revision: 1.21 $'[11:-2]
import os, AccessControl, Acquisition, sys import os, AccessControl, Acquisition, sys
from Globals import package_home, DevelopmentMode from Globals import package_home, DevelopmentMode
...@@ -85,7 +85,8 @@ class PageTemplateFile(Script, PageTemplate, Traversable): ...@@ -85,7 +85,8 @@ class PageTemplateFile(Script, PageTemplate, Traversable):
response = self.REQUEST.RESPONSE response = self.REQUEST.RESPONSE
if not response.headers.has_key('content-type'): if not response.headers.has_key('content-type'):
response.setHeader('content-type', self.content_type) response.setHeader('content-type', self.content_type)
except AttributeError: pass except AttributeError:
pass
# Execute the template in a new security context. # Execute the template in a new security context.
security=getSecurityManager() security=getSecurityManager()
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
"""Generic Python Expression Handler """Generic Python Expression Handler
""" """
__version__='$Revision: 1.8 $'[11:-2] __version__='$Revision: 1.9 $'[11:-2]
from TALES import CompilerError from TALES import CompilerError
from sys import exc_info from sys import exc_info
...@@ -78,3 +78,4 @@ class ExprTypeProxy: ...@@ -78,3 +78,4 @@ class ExprTypeProxy:
def __call__(self, text): def __call__(self, text):
return self._handler(self._name, text, return self._handler(self._name, text,
self._econtext._engine)(self._econtext) self._econtext._engine)(self._econtext)
...@@ -15,10 +15,12 @@ ...@@ -15,10 +15,12 @@
An implementation of a generic TALES engine An implementation of a generic TALES engine
""" """
__version__='$Revision: 1.31 $'[11:-2] __version__='$Revision: 1.32 $'[11:-2]
import re, sys, ZTUtils import re, sys, ZTUtils
from MultiMapping import MultiMapping from MultiMapping import MultiMapping
from DocumentTemplate.DT_Util import ustr
from GlobalTranslationService import getGlobalTranslationService
StringType = type('') StringType = type('')
...@@ -222,11 +224,11 @@ class Context: ...@@ -222,11 +224,11 @@ class Context:
def evaluateBoolean(self, expr): def evaluateBoolean(self, expr):
return not not self.evaluate(expr) return not not self.evaluate(expr)
def evaluateText(self, expr, None=None): def evaluateText(self, expr):
text = self.evaluate(expr) text = self.evaluate(expr)
if text is Default or text is None: if text is Default or text is None:
return text return text
return str(text) return ustr(text)
def evaluateStructure(self, expr): def evaluateStructure(self, expr):
return self.evaluate(expr) return self.evaluate(expr)
...@@ -249,6 +251,11 @@ class Context: ...@@ -249,6 +251,11 @@ class Context:
def setPosition(self, position): def setPosition(self, position):
self.position = position self.position = position
def translate(self, domain, msgid, mapping=None,
context=None, target_language=None):
return getGlobalTranslationService().translate(
domain, msgid, mapping=mapping,
context=context, target_language=target_language)
class TALESTracebackSupplement: class TALESTracebackSupplement:
...@@ -282,3 +289,4 @@ class SimpleExpr: ...@@ -282,3 +289,4 @@ class SimpleExpr:
return self._name, self._expr return self._name, self._expr
def __repr__(self): def __repr__(self):
return '<SimpleExpr %s %s>' % (self._name, `self._expr`) return '<SimpleExpr %s %s>' % (self._name, `self._expr`)
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
Zope object encapsulating a Page Template. Zope object encapsulating a Page Template.
""" """
__version__='$Revision: 1.43 $'[11:-2] __version__='$Revision: 1.44 $'[11:-2]
import os, AccessControl, Acquisition, sys import os, AccessControl, Acquisition, sys
from types import StringType from types import StringType
...@@ -369,3 +369,4 @@ def initialize(context): ...@@ -369,3 +369,4 @@ def initialize(context):
) )
context.registerHelp() context.registerHelp()
context.registerHelpTitle('Zope Help') context.registerHelpTitle('Zope Help')
...@@ -34,3 +34,4 @@ class harness2(harness1): ...@@ -34,3 +34,4 @@ class harness2(harness1):
assert aargs == args, "Harness method arguments" assert aargs == args, "Harness method arguments"
assert akwargs == kwargs, "Harness method keyword args" assert akwargs == kwargs, "Harness method keyword args"
return result return result
<html>
<body>
<head>
<p i18n:translate="foobar">baz</p>
</head>
</body>
</html>
<html>
<body>
<head>
<p i18n:domain="foo" i18n:translate="bar">baz</p>
</head>
</body>
</html>
<html>
<body>
<head>
<p tal:content="python:u'e acute=\xe9'">e acute here</p>
</head>
</body>
</html>
<html>
<body>
<head>
<p>foobar</p>
</head>
</body>
</html>
<html>
<body>
<head>
<p>[foo](bar)</p>
</head>
</body>
</html>
<html>
<body>
<head>
<p>e acute=é</p>
</head>
</body>
</html>
...@@ -140,3 +140,4 @@ def test_suite(): ...@@ -140,3 +140,4 @@ def test_suite():
if __name__=='__main__': if __name__=='__main__':
main() main()
...@@ -15,8 +15,9 @@ import os, sys, unittest ...@@ -15,8 +15,9 @@ import os, sys, unittest
from Products.PageTemplates.tests import util from Products.PageTemplates.tests import util
from Products.PageTemplates.PageTemplate import PageTemplate from Products.PageTemplates.PageTemplate import PageTemplate
import ZODB from Products.PageTemplates.GlobalTranslationService import \
from AccessControl import User, SecurityManager setGlobalTranslationService
from AccessControl import SecurityManager
from AccessControl.SecurityManagement import noSecurityManager from AccessControl.SecurityManagement import noSecurityManager
from Acquisition import Implicit from Acquisition import Implicit
...@@ -26,6 +27,10 @@ class AqPageTemplate(Implicit, PageTemplate): ...@@ -26,6 +27,10 @@ class AqPageTemplate(Implicit, PageTemplate):
class Folder(util.Base): class Folder(util.Base):
pass pass
class TestTranslationService:
def translate(self, domain, msgid, *args, **kw):
return "[%s](%s)" % (domain, msgid)
class UnitTestSecurityPolicy: class UnitTestSecurityPolicy:
""" """
...@@ -69,6 +74,14 @@ class HTMLTests(unittest.TestCase): ...@@ -69,6 +74,14 @@ class HTMLTests(unittest.TestCase):
out = apply(t, args, kwargs) out = apply(t, args, kwargs)
util.check_html(expect, out) util.check_html(expect, out)
def assert_expected_unicode(self, t, fname, *args, **kwargs):
t.write(util.read_input(fname))
assert not t._v_errors, 'Template errors: %s' % t._v_errors
expect = util.read_output(fname)
expect = unicode(expect, 'utf8')
out = apply(t, args, kwargs)
util.check_html(expect, out)
def getProducts(self): def getProducts(self):
return [ return [
{'description': 'This is the tee for those who LOVE Zope. ' {'description': 'This is the tee for those who LOVE Zope. '
...@@ -126,8 +139,20 @@ class HTMLTests(unittest.TestCase): ...@@ -126,8 +139,20 @@ class HTMLTests(unittest.TestCase):
def checkBatchIteration(self): def checkBatchIteration(self):
self.assert_expected(self.folder.t, 'CheckBatchIteration.html') self.assert_expected(self.folder.t, 'CheckBatchIteration.html')
def checkUnicodeInserts(self):
self.assert_expected_unicode(self.folder.t, 'CheckUnicodeInserts.html')
def checkI18nTranslate(self):
self.assert_expected(self.folder.t, 'CheckI18nTranslate.html')
def checkI18nTranslateHooked(self):
old_ts = setGlobalTranslationService(TestTranslationService())
self.assert_expected(self.folder.t, 'CheckI18nTranslateHooked.html')
setGlobalTranslationService(old_ts)
def test_suite(): def test_suite():
return unittest.makeSuite(HTMLTests, 'check') return unittest.makeSuite(HTMLTests, 'check')
if __name__=='__main__': if __name__=='__main__':
unittest.main(defaultTest='test_suite') main()
...@@ -4,6 +4,16 @@ from Products.PageTemplates import TALES ...@@ -4,6 +4,16 @@ from Products.PageTemplates import TALES
from Products.PageTemplates.tests import harness1 from Products.PageTemplates.tests import harness1
import string import string
class DummyUnicodeExpr:
'''Dummy expression type handler returning unicode'''
def __init__(self, name, expr, engine):
self._name = name
self._expr = expr
def __call__(self, econtext):
return unicode(self._expr, 'latin1')
def __repr__(self):
return '<SimpleExpr %s %s>' % (self._name, `self._expr`)
class TALESTests(unittest.TestCase): class TALESTests(unittest.TestCase):
def testIterator0(self): def testIterator0(self):
...@@ -77,6 +87,7 @@ class TALESTests(unittest.TestCase): ...@@ -77,6 +87,7 @@ class TALESTests(unittest.TestCase):
def getContext(self, **kws): def getContext(self, **kws):
e = TALES.Engine() e = TALES.Engine()
e.registerType('simple', TALES.SimpleExpr) e.registerType('simple', TALES.SimpleExpr)
e.registerType('unicode', DummyUnicodeExpr)
return apply(e.getContext, (), kws) return apply(e.getContext, (), kws)
def testContext0(self): def testContext0(self):
...@@ -85,6 +96,11 @@ class TALESTests(unittest.TestCase): ...@@ -85,6 +96,11 @@ class TALESTests(unittest.TestCase):
assert se == ('simple', 'x'), ( assert se == ('simple', 'x'), (
'Improperly evaluated expression %s.' % `se`) 'Improperly evaluated expression %s.' % `se`)
def testContextUnicode(self):
'''Test evaluateText on unicode-returning expressions'''
se = self.getContext().evaluateText('unicode:\xe9')
self.assertEqual(se, u'\xe9')
def testVariables(self): def testVariables(self):
'''Test variables''' '''Test variables'''
ctxt = self.getContext() ctxt = self.getContext()
...@@ -114,4 +130,4 @@ def test_suite(): ...@@ -114,4 +130,4 @@ def test_suite():
return unittest.makeSuite(TALESTests) return unittest.makeSuite(TALESTests)
if __name__=='__main__': if __name__=='__main__':
unittest.main(defaultTest='test_suite') main()
...@@ -14,8 +14,8 @@ ...@@ -14,8 +14,8 @@
import re, ST, STDOM import re, ST, STDOM
from STletters import letters from STletters import letters
StringType=type('') from types import StringType, UnicodeType, ListType
ListType=type([]) StringTypes = (StringType, UnicodeType)
class StructuredTextExample(ST.StructuredTextParagraph): class StructuredTextExample(ST.StructuredTextParagraph):
"""Represents a section of document with literal text, as for examples""" """Represents a section of document with literal text, as for examples"""
...@@ -235,7 +235,7 @@ class DocumentClass: ...@@ -235,7 +235,7 @@ class DocumentClass:
] ]
def __call__(self, doc): def __call__(self, doc):
if type(doc) is type(''): if type(doc) in StringTypes:
doc=ST.StructuredText(doc) doc=ST.StructuredText(doc)
doc.setSubparagraphs(self.color_paragraphs( doc.setSubparagraphs(self.color_paragraphs(
doc.getSubparagraphs())) doc.getSubparagraphs()))
...@@ -245,7 +245,7 @@ class DocumentClass: ...@@ -245,7 +245,7 @@ class DocumentClass:
return doc return doc
def parse(self, raw_string, text_type, def parse(self, raw_string, text_type,
type=type, st=type(''), lt=type([])): type=type, sts=StringTypes, lt=type([])):
""" """
Parse accepts a raw_string, an expr to test the raw_string, Parse accepts a raw_string, an expr to test the raw_string,
...@@ -261,7 +261,7 @@ class DocumentClass: ...@@ -261,7 +261,7 @@ class DocumentClass:
tmp = [] # the list to be returned if raw_string is split tmp = [] # the list to be returned if raw_string is split
append=tmp.append append=tmp.append
if type(text_type) is st: text_type=getattr(self, text_type) if type(text_type) in sts: text_type=getattr(self, text_type)
while 1: while 1:
t = text_type(raw_string) t = text_type(raw_string)
...@@ -272,7 +272,7 @@ class DocumentClass: ...@@ -272,7 +272,7 @@ class DocumentClass:
if start: append(raw_string[0:start]) if start: append(raw_string[0:start])
tt=type(t) tt=type(t)
if tt is st: if tt in sts:
# if we get a string back, add it to text to be parsed # if we get a string back, add it to text to be parsed
raw_string = t+raw_string[end:len(raw_string)] raw_string = t+raw_string[end:len(raw_string)]
else: else:
...@@ -299,12 +299,12 @@ class DocumentClass: ...@@ -299,12 +299,12 @@ class DocumentClass:
for text_type in types: for text_type in types:
if type(str) is StringType: if type(str) in StringTypes:
str = self.parse(str, text_type) str = self.parse(str, text_type)
elif type(str) is ListType: elif type(str) is ListType:
r=[]; a=r.append r=[]; a=r.append
for s in str: for s in str:
if type(s) is StringType: if type(s) in StringTypes:
s=self.parse(s, text_type) s=self.parse(s, text_type)
if type(s) is ListType: r[len(r):]=s if type(s) is ListType: r[len(r):]=s
else: a(s) else: a(s)
...@@ -327,7 +327,7 @@ class DocumentClass: ...@@ -327,7 +327,7 @@ class DocumentClass:
def color_paragraphs(self, raw_paragraphs, def color_paragraphs(self, raw_paragraphs,
type=type, sequence_types=(type([]), type(())), type=type, sequence_types=(type([]), type(())),
st=type('')): sts=StringTypes):
result=[] result=[]
for paragraph in raw_paragraphs: for paragraph in raw_paragraphs:
...@@ -336,7 +336,7 @@ class DocumentClass: ...@@ -336,7 +336,7 @@ class DocumentClass:
continue continue
for pt in self.paragraph_types: for pt in self.paragraph_types:
if type(pt) is st: if type(pt) in sts:
# grab the corresponding function # grab the corresponding function
pt=getattr(self, pt) pt=getattr(self, pt)
# evaluate the paragraph # evaluate the paragraph
......
...@@ -15,8 +15,8 @@ import re, ST, STDOM ...@@ -15,8 +15,8 @@ import re, ST, STDOM
from STletters import letters, digits, literal_punc, under_punc,\ from STletters import letters, digits, literal_punc, under_punc,\
strongem_punc, phrase_delimiters,dbl_quoted_punc strongem_punc, phrase_delimiters,dbl_quoted_punc
StringType=type('') from types import StringType, UnicodeType, ListType
ListType=type([]) StringTypes = (StringType, UnicodeType)
def flatten(obj, append): def flatten(obj, append):
if obj.getNodeType()==STDOM.TEXT_NODE: if obj.getNodeType()==STDOM.TEXT_NODE:
...@@ -308,7 +308,7 @@ class DocumentClass: ...@@ -308,7 +308,7 @@ class DocumentClass:
] ]
def __call__(self, doc): def __call__(self, doc):
if type(doc) is type(''): if type(doc) in StringTypes:
doc=ST.StructuredText(doc) doc=ST.StructuredText(doc)
doc.setSubparagraphs(self.color_paragraphs( doc.setSubparagraphs(self.color_paragraphs(
doc.getSubparagraphs())) doc.getSubparagraphs()))
...@@ -318,7 +318,7 @@ class DocumentClass: ...@@ -318,7 +318,7 @@ class DocumentClass:
return doc return doc
def parse(self, raw_string, text_type, def parse(self, raw_string, text_type,
type=type, st=type(''), lt=type([])): type=type, sts=StringTypes, lt=type([])):
""" """
Parse accepts a raw_string, an expr to test the raw_string, Parse accepts a raw_string, an expr to test the raw_string,
...@@ -334,7 +334,7 @@ class DocumentClass: ...@@ -334,7 +334,7 @@ class DocumentClass:
tmp = [] # the list to be returned if raw_string is split tmp = [] # the list to be returned if raw_string is split
append=tmp.append append=tmp.append
if type(text_type) is st: text_type=getattr(self, text_type) if type(text_type) in sts: text_type=getattr(self, text_type)
while 1: while 1:
t = text_type(raw_string) t = text_type(raw_string)
...@@ -345,7 +345,7 @@ class DocumentClass: ...@@ -345,7 +345,7 @@ class DocumentClass:
if start: append(raw_string[0:start]) if start: append(raw_string[0:start])
tt=type(t) tt=type(t)
if tt is st: if tt in sts:
# if we get a string back, add it to text to be parsed # if we get a string back, add it to text to be parsed
raw_string = t+raw_string[end:len(raw_string)] raw_string = t+raw_string[end:len(raw_string)]
else: else:
...@@ -372,12 +372,12 @@ class DocumentClass: ...@@ -372,12 +372,12 @@ class DocumentClass:
for text_type in types: for text_type in types:
if type(str) is StringType: if type(str) in StringTypes:
str = self.parse(str, text_type) str = self.parse(str, text_type)
elif type(str) is ListType: elif type(str) is ListType:
r=[]; a=r.append r=[]; a=r.append
for s in str: for s in str:
if type(s) is StringType: if type(s) in StringTypes:
s=self.parse(s, text_type) s=self.parse(s, text_type)
if type(s) is ListType: r[len(r):]=s if type(s) is ListType: r[len(r):]=s
else: a(s) else: a(s)
...@@ -400,7 +400,7 @@ class DocumentClass: ...@@ -400,7 +400,7 @@ class DocumentClass:
def color_paragraphs(self, raw_paragraphs, def color_paragraphs(self, raw_paragraphs,
type=type, sequence_types=(type([]), type(())), type=type, sequence_types=(type([]), type(())),
st=type('')): sts=StringTypes):
result=[] result=[]
for paragraph in raw_paragraphs: for paragraph in raw_paragraphs:
if paragraph.getNodeName() != 'StructuredTextParagraph': if paragraph.getNodeName() != 'StructuredTextParagraph':
...@@ -408,7 +408,7 @@ class DocumentClass: ...@@ -408,7 +408,7 @@ class DocumentClass:
continue continue
for pt in self.paragraph_types: for pt in self.paragraph_types:
if type(pt) is st: if type(pt) in sts:
# grab the corresponding function # grab the corresponding function
pt=getattr(self, pt) pt=getattr(self, pt)
# evaluate the paragraph # evaluate the paragraph
......
...@@ -16,6 +16,9 @@ DOM implementation in StructuredText : Read-Only methods ...@@ -16,6 +16,9 @@ DOM implementation in StructuredText : Read-Only methods
All standard Zope objects support DOM to a limited extent. All standard Zope objects support DOM to a limited extent.
""" """
from types import StringType, UnicodeType
StringTypes = (StringType, UnicodeType)
# Node type codes # Node type codes
# --------------- # ---------------
...@@ -81,7 +84,7 @@ class ParentNode: ...@@ -81,7 +84,7 @@ class ParentNode:
the child access methods of the DOM. the child access methods of the DOM.
""" """
def getChildNodes(self, type=type, st=type('')): def getChildNodes(self, type=type, sts=StringTypes):
""" """
Returns a NodeList that contains all children of this node. Returns a NodeList that contains all children of this node.
If there are no children, this is a empty NodeList If there are no children, this is a empty NodeList
...@@ -89,12 +92,12 @@ class ParentNode: ...@@ -89,12 +92,12 @@ class ParentNode:
r=[] r=[]
for n in self.getChildren(): for n in self.getChildren():
if type(n) is st: n=TextNode(n) if type(n) in sts: n=TextNode(n)
r.append(n.__of__(self)) r.append(n.__of__(self))
return NodeList(r) return NodeList(r)
def getFirstChild(self, type=type, st=type('')): def getFirstChild(self, type=type, sts=StringTypes):
""" """
The first child of this node. If there is no such node The first child of this node. If there is no such node
this returns None this returns None
...@@ -106,12 +109,12 @@ class ParentNode: ...@@ -106,12 +109,12 @@ class ParentNode:
n=children[0] n=children[0]
if type(n) is st: if type(n) in sts:
n=TextNode(n) n=TextNode(n)
return n.__of__(self) return n.__of__(self)
def getLastChild(self, type=type, st=type('')): def getLastChild(self, type=type, sts=StringTypes):
""" """
The last child of this node. If there is no such node The last child of this node. If there is no such node
this returns None. this returns None.
...@@ -119,21 +122,21 @@ class ParentNode: ...@@ -119,21 +122,21 @@ class ParentNode:
children = self.getChildren() children = self.getChildren()
if not children: return None if not children: return None
n=chidren[-1] n=chidren[-1]
if type(n) is st: n=TextNode(n) if type(n) in sts: n=TextNode(n)
return n.__of__(self) return n.__of__(self)
""" """
create aliases for all above functions in the pythony way. create aliases for all above functions in the pythony way.
""" """
def _get_ChildNodes(self, type=type, st=type('')): def _get_ChildNodes(self, type=type, sts=StringTypes):
return self.getChildNodes(type,st) return self.getChildNodes(type,sts)
def _get_FirstChild(self, type=type, st=type('')): def _get_FirstChild(self, type=type, sts=StringTypes):
return self.getFirstChild(type,st) return self.getFirstChild(type,sts)
def _get_LastChild(self, type=type, st=type('')): def _get_LastChild(self, type=type, sts=StringTypes):
return self.getLastChild(type,st) return self.getLastChild(type,sts)
class NodeWrapper(ParentNode): class NodeWrapper(ParentNode):
""" """
...@@ -167,7 +170,7 @@ class NodeWrapper(ParentNode): ...@@ -167,7 +170,7 @@ class NodeWrapper(ParentNode):
def getPreviousSibling(self, def getPreviousSibling(self,
type=type, type=type,
st=type(''), sts=StringTypes,
getattr=getattr, getattr=getattr,
None=None): None=None):
...@@ -190,13 +193,13 @@ class NodeWrapper(ParentNode): ...@@ -190,13 +193,13 @@ class NodeWrapper(ParentNode):
try: n=children[index] try: n=children[index]
except IndexError: return None except IndexError: return None
else: else:
if type(n) is st: if type(n) in sts:
n=TextNode(n) n=TextNode(n)
n._DOMIndex=index n._DOMIndex=index
return n.__of__(self) return n.__of__(self)
def getNextSibling(self, type=type, st=type('')): def getNextSibling(self, type=type, sts=StringTypes):
""" """
The node immediately preceding this node. If The node immediately preceding this node. If
there is no such node, this returns None. there is no such node, this returns None.
...@@ -216,7 +219,7 @@ class NodeWrapper(ParentNode): ...@@ -216,7 +219,7 @@ class NodeWrapper(ParentNode):
except IndexError: except IndexError:
return None return None
else: else:
if type(n) is st: if type(n) in sts:
n=TextNode(n) n=TextNode(n)
n._DOMIndex=index n._DOMIndex=index
return n.__of__(self) return n.__of__(self)
...@@ -239,14 +242,14 @@ class NodeWrapper(ParentNode): ...@@ -239,14 +242,14 @@ class NodeWrapper(ParentNode):
def _get_PreviousSibling(self, def _get_PreviousSibling(self,
type=type, type=type,
st=type(''), sts=StringTypes,
getattr=getattr, getattr=getattr,
None=None): None=None):
return self.getPreviousSibling(type,st,getattr,None) return self.getPreviousSibling(type,sts,getattr,None)
def _get_NextSibling(self, type=type, st=type('')): def _get_NextSibling(self, type=type, sts=StringTypes):
return self.getNextSibling(type,st) return self.getNextSibling(type,sts)
def _get_OwnerDocument(self): def _get_OwnerDocument(self):
return self.getOwnerDocument() return self.getOwnerDocument()
...@@ -288,7 +291,7 @@ class Node(ParentNode): ...@@ -288,7 +291,7 @@ class Node(ParentNode):
def getPreviousSibling(self, def getPreviousSibling(self,
type=type, type=type,
st=type(''), sts=StringTypes,
getattr=getattr, getattr=getattr,
None=None): None=None):
""" """
...@@ -296,7 +299,7 @@ class Node(ParentNode): ...@@ -296,7 +299,7 @@ class Node(ParentNode):
there is no such node, this returns None. there is no such node, this returns None.
""" """
def getNextSibling(self, type=type, st=type('')): def getNextSibling(self, type=type, sts=StringTypes):
""" """
The node immediately preceding this node. If The node immediately preceding this node. If
there is no such node, this returns None. there is no such node, this returns None.
...@@ -342,13 +345,13 @@ class Node(ParentNode): ...@@ -342,13 +345,13 @@ class Node(ParentNode):
def _get_PreviousSibling(self, def _get_PreviousSibling(self,
type=type, type=type,
st=type(''), sts=StringTypes,
getattr=getattr, getattr=getattr,
None=None): None=None):
return self.getPreviousSibling(type,st,getattr,None) return self.getPreviousSibling(type,sts,getattr,None)
def _get_NextSibling(self, type=type, st=type('')): def _get_NextSibling(self, type=type, sts=StringTypes):
return self.getNextSibling() return self.getNextSibling()
def _get_Attributes(self): def _get_Attributes(self):
...@@ -407,10 +410,10 @@ class Element(Node): ...@@ -407,10 +410,10 @@ class Element(Node):
"""A code representing the type of the node.""" """A code representing the type of the node."""
return ELEMENT_NODE return ELEMENT_NODE
def getNodeValue(self, type=type, st=type('')): def getNodeValue(self, type=type, sts=StringTypes):
r=[] r=[]
for c in self.getChildren(): for c in self.getChildren():
if type(c) is not st: if type(c) not in sts:
c=c.getNodeValue() c=c.getNodeValue()
r.append(c) r.append(c)
return ''.join(r) return ''.join(r)
...@@ -480,8 +483,8 @@ class Element(Node): ...@@ -480,8 +483,8 @@ class Element(Node):
def _get_NodeType(self): def _get_NodeType(self):
return self.getNodeType() return self.getNodeType()
def _get_NodeValue(self, type=type, st=type('')): def _get_NodeValue(self, type=type, sts=StringTypes):
return self.getNodeValue(type,st) return self.getNodeValue(type,sts)
def _get_ParentNode(self): def _get_ParentNode(self):
return self.getParentNode() return self.getParentNode()
...@@ -517,7 +520,7 @@ class NodeList: ...@@ -517,7 +520,7 @@ class NodeList:
def __init__(self,list=None): def __init__(self,list=None):
self._data = list or [] self._data = list or []
def __getitem__(self, index, type=type, st=type('')): def __getitem__(self, index, type=type, sts=StringTypes):
return self._data[index] return self._data[index]
def __getslice__(self, i, j): def __getslice__(self, i, j):
......
...@@ -17,14 +17,14 @@ from StructuredText import html_with_references, HTML, html_quote ...@@ -17,14 +17,14 @@ from StructuredText import html_with_references, HTML, html_quote
from ST import Basic from ST import Basic
import DocBookClass import DocBookClass
import HTMLWithImages import HTMLWithImages
from types import StringType from types import StringType, UnicodeType
import DocumentWithImages import DocumentWithImages
ClassicHTML=HTML ClassicHTML=HTML
HTMLNG=HTMLClass.HTMLClass() HTMLNG=HTMLClass.HTMLClass()
def HTML(src, level=1): def HTML(src, level=1):
if isinstance(src, StringType): if isinstance(src, StringType) or isinstance(src, UnicodeType):
return ClassicHTML(src, level) return ClassicHTML(src, level)
return HTMLNG(src, level) return HTMLNG(src, level)
......
...@@ -18,6 +18,7 @@ from StructuredText import StructuredText ...@@ -18,6 +18,7 @@ from StructuredText import StructuredText
from StructuredText import HTMLClass from StructuredText import HTMLClass
from StructuredText.StructuredText import HTML from StructuredText.StructuredText import HTML
import sys, os, unittest, cStringIO import sys, os, unittest, cStringIO
from types import UnicodeType
from OFS import ndiff from OFS import ndiff
""" """
...@@ -52,6 +53,8 @@ class StructuredTextTests(unittest.TestCase): ...@@ -52,6 +53,8 @@ class StructuredTextTests(unittest.TestCase):
raw_text = readFile(regressions,f) raw_text = readFile(regressions,f)
assert StructuredText.StructuredText(raw_text),\ assert StructuredText.StructuredText(raw_text),\
'StructuredText failed on %s' % f 'StructuredText failed on %s' % f
assert StructuredText.StructuredText(unicode(raw_text)),\
'StructuredText failed on Unicode %s' % f
def testStructuredTextNG(self): def testStructuredTextNG(self):
""" testing StructuredTextNG """ """ testing StructuredTextNG """
...@@ -60,6 +63,8 @@ class StructuredTextTests(unittest.TestCase): ...@@ -60,6 +63,8 @@ class StructuredTextTests(unittest.TestCase):
raw_text = readFile(regressions,f) raw_text = readFile(regressions,f)
assert ST.StructuredText(raw_text),\ assert ST.StructuredText(raw_text),\
'StructuredText failed on %s' % f 'StructuredText failed on %s' % f
assert ST.StructuredText(unicode(raw_text)),\
'StructuredText failed on Unicode %s' % f
def testDocumentClass(self): def testDocumentClass(self):
...@@ -131,12 +136,25 @@ class BasicTests(unittest.TestCase): ...@@ -131,12 +136,25 @@ class BasicTests(unittest.TestCase):
def _test(self,stxtxt , expected): def _test(self,stxtxt , expected):
res = HTML(stxtxt,level=1,header=0) if not isinstance(stxtxt, UnicodeType):
res = HTML(stxtxt,level=1,header=0)
if res.find(expected)==-1:
print "Text: ",stxtxt
print "Converted:",res
print "Expected: ",expected
raise AssertionError,"basic test failed for '%s'" % stxtxt
if isinstance(stxtxt, UnicodeType):
ustxtxt = stxtxt
else:
ustxtxt = unicode(stxtxt)
res = HTML(ustxtxt,level=1,header=0)
if res.find(expected)==-1: if res.find(expected)==-1:
print "Text: ",stxtxt print "Text: ",stxtxt.encode('latin-1')
print "Converted:",res print "Converted:",res.encode('latin-1')
print "Expected: ",expected print "Expected: ",expected.encode('latin-1')
raise AssertionError,"basic test failed for '%s'" % stxtxt raise AssertionError, ("basic test failed for Unicode '%s'"
% stxtxt)
def testUnderline(self): def testUnderline(self):
...@@ -192,6 +210,14 @@ class BasicTests(unittest.TestCase): ...@@ -192,6 +210,14 @@ class BasicTests(unittest.TestCase):
'<code>"literal":http://www.zope.org/.</code>') '<code>"literal":http://www.zope.org/.</code>')
def XXXtestUnicodeContent(self):
# This fails because ST uses the default locale to get "letters"
# whereas it should use \w+ and re.U if the string is Unicode.
#self._test(u"h\xe9 **y\xe9** xx",
# u"h\xe9 <strong>y\xe9</strong> xx")
pass
def test_suite(): def test_suite():
suite = unittest.TestSuite() suite = unittest.TestSuite()
suite.addTest( unittest.makeSuite( StructuredTextTests ) ) suite.addTest( unittest.makeSuite( StructuredTextTests ) )
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
# 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.
# #
############################################################################## ##############################################################################
""" """
...@@ -18,13 +18,20 @@ Dummy TALES engine so that I can test out the TAL implementation. ...@@ -18,13 +18,20 @@ Dummy TALES engine so that I can test out the TAL implementation.
import re import re
import sys import sys
import driver
from TALDefs import NAME_RE, TALESError, ErrorInfo from TALDefs import NAME_RE, TALESError, ErrorInfo
from ITALES import ITALESCompiler, ITALESEngine
class Default: from DocumentTemplate.DT_Util import ustr
try:
from Zope.I18n.ITranslationService import ITranslationService
from Zope.I18n.IDomain import IDomain
except ImportError:
# Before 2.7
class ITranslationService: pass
class IDomain: pass
class _Default:
pass pass
Default = Default() Default = _Default()
name_match = re.compile(r"(?s)(%s):(.*)\Z" % NAME_RE).match name_match = re.compile(r"(?s)(%s):(.*)\Z" % NAME_RE).match
...@@ -36,6 +43,8 @@ class DummyEngine: ...@@ -36,6 +43,8 @@ class DummyEngine:
position = None position = None
source_file = None source_file = None
__implements__ = ITALESCompiler, ITALESEngine
def __init__(self, macros=None): def __init__(self, macros=None):
if macros is None: if macros is None:
macros = {} macros = {}
...@@ -43,6 +52,7 @@ class DummyEngine: ...@@ -43,6 +52,7 @@ class DummyEngine:
dict = {'nothing': None, 'default': Default} dict = {'nothing': None, 'default': Default}
self.locals = self.globals = dict self.locals = self.globals = dict
self.stack = [dict] self.stack = [dict]
self.translationService = DummyTranslationService()
def getCompilerError(self): def getCompilerError(self):
return CompilerError return CompilerError
...@@ -90,13 +100,7 @@ class DummyEngine: ...@@ -90,13 +100,7 @@ class DummyEngine:
if type in ("string", "str"): if type in ("string", "str"):
return expr return expr
if type in ("path", "var", "global", "local"): if type in ("path", "var", "global", "local"):
expr = expr.strip() return self.evaluatePathOrVar(expr)
if self.locals.has_key(expr):
return self.locals[expr]
elif self.globals.has_key(expr):
return self.globals[expr]
else:
raise TALESError("unknown variable: %s" % `expr`)
if type == "not": if type == "not":
return not self.evaluate(expr) return not self.evaluate(expr)
if type == "exists": if type == "exists":
...@@ -116,6 +120,15 @@ class DummyEngine: ...@@ -116,6 +120,15 @@ class DummyEngine:
return '%s (%s,%s)' % (self.source_file, lineno, offset) return '%s (%s,%s)' % (self.source_file, lineno, offset)
raise TALESError("unrecognized expression: " + `expression`) raise TALESError("unrecognized expression: " + `expression`)
def evaluatePathOrVar(self, expr):
expr = expr.strip()
if self.locals.has_key(expr):
return self.locals[expr]
elif self.globals.has_key(expr):
return self.globals[expr]
else:
raise TALESError("unknown variable: %s" % `expr`)
def evaluateValue(self, expr): def evaluateValue(self, expr):
return self.evaluate(expr) return self.evaluate(expr)
...@@ -125,7 +138,7 @@ class DummyEngine: ...@@ -125,7 +138,7 @@ class DummyEngine:
def evaluateText(self, expr): def evaluateText(self, expr):
text = self.evaluate(expr) text = self.evaluate(expr)
if text is not None and text is not Default: if text is not None and text is not Default:
text = str(text) text = ustr(text)
return text return text
def evaluateStructure(self, expr): def evaluateStructure(self, expr):
...@@ -146,6 +159,7 @@ class DummyEngine: ...@@ -146,6 +159,7 @@ class DummyEngine:
macro = self.macros[localName] macro = self.macros[localName]
else: else:
# External macro # External macro
import driver
program, macros = driver.compilefile(file) program, macros = driver.compilefile(file)
macro = macros.get(localName) macro = macros.get(localName)
if not macro: if not macro:
...@@ -157,6 +171,7 @@ class DummyEngine: ...@@ -157,6 +171,7 @@ class DummyEngine:
file, localName = self.findMacroFile(macroName) file, localName = self.findMacroFile(macroName)
if not file: if not file:
return file, localName return file, localName
import driver
doc = driver.parsefile(file) doc = driver.parsefile(file)
return doc, localName return doc, localName
...@@ -183,6 +198,10 @@ class DummyEngine: ...@@ -183,6 +198,10 @@ class DummyEngine:
def getDefault(self): def getDefault(self):
return Default return Default
def translate(self, domain, msgid, mapping):
return self.translationService.translate(domain, msgid, mapping)
class Iterator: class Iterator:
def __init__(self, name, seq, engine): def __init__(self, name, seq, engine):
...@@ -200,3 +219,31 @@ class Iterator: ...@@ -200,3 +219,31 @@ class Iterator:
self.nextIndex = i+1 self.nextIndex = i+1
self.engine.setLocal(self.name, item) self.engine.setLocal(self.name, item)
return 1 return 1
class DummyDomain:
__implements__ = IDomain
def translate(self, msgid, mapping=None, context=None,
target_language=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.
def repl(m, mapping=mapping):
return mapping[m.group(m.lastindex).lower()]
cre = re.compile(r'\$(?:([_A-Z]\w*)|\{([_A-Z]\w*)\})')
return cre.sub(repl, msgid.upper())
class DummyTranslationService:
__implements__ = ITranslationService
def translate(self, domain, msgid, mapping=None, context=None,
target_language=None):
# Ignore domain
return self.getDomain(domain).translate(msgid, mapping, context,
target_language)
def getDomain(self, domain):
return DummyDomain()
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
# 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.
# #
############################################################################## ##############################################################################
""" """
...@@ -18,8 +18,9 @@ Parse HTML and compile to TALInterpreter intermediate code. ...@@ -18,8 +18,9 @@ Parse HTML and compile to TALInterpreter intermediate code.
import sys import sys
from TALGenerator import TALGenerator from TALGenerator import TALGenerator
from TALDefs import ZOPE_METAL_NS, ZOPE_TAL_NS, METALError, TALError
from HTMLParser import HTMLParser, HTMLParseError from HTMLParser import HTMLParser, HTMLParseError
from TALDefs import \
ZOPE_METAL_NS, ZOPE_TAL_NS, ZOPE_I18N_NS, METALError, TALError, I18NError
BOOLEAN_HTML_ATTRS = [ BOOLEAN_HTML_ATTRS = [
# List of Boolean attributes in HTML that may be given in # List of Boolean attributes in HTML that may be given in
...@@ -106,13 +107,20 @@ class HTMLTALParser(HTMLParser): ...@@ -106,13 +107,20 @@ class HTMLTALParser(HTMLParser):
self.gen = gen self.gen = gen
self.tagstack = [] self.tagstack = []
self.nsstack = [] 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): def parseFile(self, file):
f = open(file) f = open(file)
data = f.read() data = f.read()
f.close() f.close()
self.parseString(data) try:
self.parseString(data)
except TALError, e:
e.setFile(file)
raise
def parseString(self, data): def parseString(self, data):
self.feed(data) self.feed(data)
...@@ -132,9 +140,10 @@ class HTMLTALParser(HTMLParser): ...@@ -132,9 +140,10 @@ class HTMLTALParser(HTMLParser):
def handle_starttag(self, tag, attrs): def handle_starttag(self, tag, attrs):
self.close_para_tags(tag) self.close_para_tags(tag)
self.scan_xmlns(attrs) 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.tagstack.append(tag)
self.gen.emitStartElement(tag, attrlist, taldict, metaldict, self.gen.emitStartElement(tag, attrlist, taldict, metaldict, i18ndict,
self.getpos()) self.getpos())
if tag in EMPTY_HTML_TAGS: if tag in EMPTY_HTML_TAGS:
self.implied_endtag(tag, -1) self.implied_endtag(tag, -1)
...@@ -142,14 +151,15 @@ class HTMLTALParser(HTMLParser): ...@@ -142,14 +151,15 @@ class HTMLTALParser(HTMLParser):
def handle_startendtag(self, tag, attrs): def handle_startendtag(self, tag, attrs):
self.close_para_tags(tag) self.close_para_tags(tag)
self.scan_xmlns(attrs) 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"): if taldict.get("content"):
self.gen.emitStartElement(tag, attrlist, taldict, metaldict, self.gen.emitStartElement(tag, attrlist, taldict, metaldict,
self.getpos()) i18ndict, self.getpos())
self.gen.emitEndElement(tag, implied=-1) self.gen.emitEndElement(tag, implied=-1)
else: else:
self.gen.emitStartElement(tag, attrlist, taldict, metaldict, self.gen.emitStartElement(tag, attrlist, taldict, metaldict,
self.getpos(), isend=1) i18ndict, self.getpos(), isend=1)
self.pop_xmlns() self.pop_xmlns()
def handle_endtag(self, tag): def handle_endtag(self, tag):
...@@ -252,7 +262,7 @@ class HTMLTALParser(HTMLParser): ...@@ -252,7 +262,7 @@ class HTMLTALParser(HTMLParser):
prefix, suffix = name.split(':', 1) prefix, suffix = name.split(':', 1)
if prefix == 'xmlns': if prefix == 'xmlns':
nsuri = self.nsdict.get(suffix) 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 return name, name, prefix
else: else:
nsuri = self.nsdict.get(prefix) nsuri = self.nsdict.get(prefix)
...@@ -260,20 +270,20 @@ class HTMLTALParser(HTMLParser): ...@@ -260,20 +270,20 @@ class HTMLTALParser(HTMLParser):
return name, suffix, 'tal' return name, suffix, 'tal'
elif nsuri == ZOPE_METAL_NS: elif nsuri == ZOPE_METAL_NS:
return name, suffix, 'metal' return name, suffix, 'metal'
elif nsuri == ZOPE_I18N_NS:
return name, suffix, 'i18n'
return name, name, 0 return name, name, 0
def process_ns(self, name, attrs): def process_ns(self, name, attrs):
attrlist = [] attrlist = []
taldict = {} taldict = {}
metaldict = {} metaldict = {}
i18ndict = {}
name, namebase, namens = self.fixname(name) name, namebase, namens = self.fixname(name)
for item in attrs: for item in attrs:
key, value = item key, value = item
key, keybase, keyns = self.fixname(key) key, keybase, keyns = self.fixname(key)
if ':' in key and not keyns: ns = keyns or namens # default to tag namespace
ns = 0
else:
ns = keyns or namens # default to tag namespace
if ns and ns != 'unknown': if ns and ns != 'unknown':
item = (key, value, ns) item = (key, value, ns)
if ns == 'tal': if ns == 'tal':
...@@ -286,7 +296,12 @@ class HTMLTALParser(HTMLParser): ...@@ -286,7 +296,12 @@ class HTMLTALParser(HTMLParser):
raise METALError("duplicate METAL attribute " + raise METALError("duplicate METAL attribute " +
`keybase`, self.getpos()) `keybase`, self.getpos())
metaldict[keybase] = value 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) attrlist.append(item)
if namens in ('metal', 'tal'): if namens in ('metal', 'tal'):
taldict['tal tag'] = namens taldict['tal tag'] = namens
return name, attrlist, taldict, metaldict return name, attrlist, taldict, metaldict, i18ndict
"""Interface that a TALES engine provides to the METAL/TAL implementation."""
try:
from Interface import Interface
from Interface.Attribute import Attribute
except:
# Before 2.7
class Interface: pass
def Attribute(*args): pass
class ITALESCompiler(Interface):
"""Compile-time interface provided by a TALES implementation.
The TAL compiler needs an instance of this interface to support
compilation of TALES expressions embedded in documents containing
TAL and METAL constructs.
"""
def getCompilerError():
"""Return the exception class raised for compilation errors.
"""
def compile(expression):
"""Return a compiled form of 'expression' for later evaluation.
'expression' is the source text of the expression.
The return value may be passed to the various evaluate*()
methods of the ITALESEngine interface. No compatibility is
required for the values of the compiled expression between
different ITALESEngine implementations.
"""
class ITALESEngine(Interface):
"""Render-time interface provided by a TALES implementation.
The TAL interpreter uses this interface to TALES to support
evaluation of the compiled expressions returned by
ITALESCompiler.compile().
"""
def getDefault():
"""Return the value of the 'default' TALES expression.
Checking a value for a match with 'default' should be done
using the 'is' operator in Python.
"""
def setPosition((lineno, offset)):
"""Inform the engine of the current position in the source file.
This is used to allow the evaluation engine to report
execution errors so that site developers can more easily
locate the offending expression.
"""
def setSourceFile(filename):
"""Inform the engine of the name of the current source file.
This is used to allow the evaluation engine to report
execution errors so that site developers can more easily
locate the offending expression.
"""
def beginScope():
"""Push a new scope onto the stack of open scopes.
"""
def endScope():
"""Pop one scope from the stack of open scopes.
"""
def evaluate(compiled_expression):
"""Evaluate an arbitrary expression.
No constraints are imposed on the return value.
"""
def evaluateBoolean(compiled_expression):
"""Evaluate an expression that must return a Boolean value.
"""
def evaluateMacro(compiled_expression):
"""Evaluate an expression that must return a macro program.
"""
def evaluateStructure(compiled_expression):
"""Evaluate an expression that must return a structured
document fragment.
The result of evaluating 'compiled_expression' must be a
string containing a parsable HTML or XML fragment. Any TAL
markup cnotained in the result string will be interpreted.
"""
def evaluateText(compiled_expression):
"""Evaluate an expression that must return text.
The returned text should be suitable for direct inclusion in
the output: any HTML or XML escaping or quoting is the
responsibility of the expression itself.
"""
def evaluateValue(compiled_expression):
"""Evaluate an arbitrary expression.
No constraints are imposed on the return value.
"""
def createErrorInfo(exception, (lineno, offset)):
"""Returns an ITALESErrorInfo object.
The returned object is used to provide information about the
error condition for the on-error handler.
"""
def setGlobal(name, value):
"""Set a global variable.
The variable will be named 'name' and have the value 'value'.
"""
def setLocal(name, value):
"""Set a local variable in the current scope.
The variable will be named 'name' and have the value 'value'.
"""
def setRepeat(name, compiled_expression):
"""
"""
def translate(domain, msgid, mapping):
"""
See ITranslationService.translate()
"""
class ITALESErrorInfo(Interface):
type = Attribute("type",
"The exception class.")
value = Attribute("value",
"The exception instance.")
lineno = Attribute("lineno",
"The line number the error occurred on in the source.")
offset = Attribute("offset",
"The character offset at which the error occurred.")
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
# 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.
# #
############################################################################## ##############################################################################
""" """
...@@ -17,13 +17,16 @@ Common definitions used by TAL and METAL compilation an transformation. ...@@ -17,13 +17,16 @@ Common definitions used by TAL and METAL compilation an transformation.
from types import ListType, TupleType from types import ListType, TupleType
TAL_VERSION = "1.3.2" from ITALES import ITALESErrorInfo
TAL_VERSION = "1.4"
XML_NS = "http://www.w3.org/XML/1998/namespace" # URI for XML namespace XML_NS = "http://www.w3.org/XML/1998/namespace" # URI for XML namespace
XMLNS_NS = "http://www.w3.org/2000/xmlns/" # URI for XML NS declarations XMLNS_NS = "http://www.w3.org/2000/xmlns/" # URI for XML NS declarations
ZOPE_TAL_NS = "http://xml.zope.org/namespaces/tal" ZOPE_TAL_NS = "http://xml.zope.org/namespaces/tal"
ZOPE_METAL_NS = "http://xml.zope.org/namespaces/metal" 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_]*" NAME_RE = "[a-zA-Z_][a-zA-Z0-9_]*"
...@@ -32,7 +35,6 @@ KNOWN_METAL_ATTRIBUTES = [ ...@@ -32,7 +35,6 @@ KNOWN_METAL_ATTRIBUTES = [
"use-macro", "use-macro",
"define-slot", "define-slot",
"fill-slot", "fill-slot",
"slot",
] ]
KNOWN_TAL_ATTRIBUTES = [ KNOWN_TAL_ATTRIBUTES = [
...@@ -47,6 +49,16 @@ KNOWN_TAL_ATTRIBUTES = [ ...@@ -47,6 +49,16 @@ KNOWN_TAL_ATTRIBUTES = [
"tal tag", "tal tag",
] ]
KNOWN_I18N_ATTRIBUTES = [
"translate",
"domain",
"target",
"source",
"attributes",
"data",
"name",
]
class TALError(Exception): class TALError(Exception):
def __init__(self, msg, position=(None, None)): def __init__(self, msg, position=(None, None)):
...@@ -54,6 +66,10 @@ class TALError(Exception): ...@@ -54,6 +66,10 @@ class TALError(Exception):
self.msg = msg self.msg = msg
self.lineno = position[0] self.lineno = position[0]
self.offset = position[1] self.offset = position[1]
self.filename = None
def setFile(self, filename):
self.filename = filename
def __str__(self): def __str__(self):
result = self.msg result = self.msg
...@@ -61,6 +77,8 @@ class TALError(Exception): ...@@ -61,6 +77,8 @@ class TALError(Exception):
result = result + ", at line %d" % self.lineno result = result + ", at line %d" % self.lineno
if self.offset is not None: if self.offset is not None:
result = result + ", column %d" % (self.offset + 1) result = result + ", column %d" % (self.offset + 1)
if self.filename is not None:
result = result + ', in file %s' % self.filename
return result return result
class METALError(TALError): class METALError(TALError):
...@@ -69,9 +87,14 @@ class METALError(TALError): ...@@ -69,9 +87,14 @@ class METALError(TALError):
class TALESError(TALError): class TALESError(TALError):
pass pass
class I18NError(TALError):
pass
class ErrorInfo: class ErrorInfo:
__implements__ = ITALESErrorInfo
def __init__(self, err, position=(None, None)): def __init__(self, err, position=(None, None)):
if isinstance(err, Exception): if isinstance(err, Exception):
self.type = err.__class__ self.type = err.__class__
......
This diff is collapsed.
This diff is collapsed.
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
# 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.
# #
############################################################################## ##############################################################################
""" """
...@@ -16,7 +16,7 @@ Parse XML and compile to TALInterpreter intermediate code. ...@@ -16,7 +16,7 @@ Parse XML and compile to TALInterpreter intermediate code.
""" """
from XMLParser import XMLParser from XMLParser import XMLParser
from TALDefs import * from TALDefs import XML_NS, ZOPE_I18N_NS, ZOPE_METAL_NS, ZOPE_TAL_NS
from TALGenerator import TALGenerator from TALGenerator import TALGenerator
class TALParser(XMLParser): class TALParser(XMLParser):
...@@ -58,13 +58,15 @@ class TALParser(XMLParser): ...@@ -58,13 +58,15 @@ class TALParser(XMLParser):
# attrs is a dict of {name: value} # attrs is a dict of {name: value}
attrlist = attrs.items() attrlist = attrs.items()
attrlist.sort() # For definiteness 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 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): def process_ns(self, name, attrlist):
taldict = {} taldict = {}
metaldict = {} metaldict = {}
i18ndict = {}
fixedattrlist = [] fixedattrlist = []
name, namebase, namens = self.fixname(name) name, namebase, namens = self.fixname(name)
for key, value in attrlist: for key, value in attrlist:
...@@ -77,10 +79,14 @@ class TALParser(XMLParser): ...@@ -77,10 +79,14 @@ class TALParser(XMLParser):
elif ns == 'tal': elif ns == 'tal':
taldict[keybase] = value taldict[keybase] = value
item = item + ("tal",) item = item + ("tal",)
elif ns == 'i18n':
assert 0, "dealing with i18n: " + `(keybase, value)`
i18ndict[keybase] = value
item = item + ('i18n',)
fixedattrlist.append(item) fixedattrlist.append(item)
if namens in ('metal', 'tal'): if namens in ('metal', 'tal', 'i18n'):
taldict['tal tag'] = namens taldict['tal tag'] = namens
return name, fixedattrlist, taldict, metaldict return name, fixedattrlist, taldict, metaldict, i18ndict
def xmlnsattrs(self): def xmlnsattrs(self):
newlist = [] newlist = []
...@@ -89,7 +95,7 @@ class TALParser(XMLParser): ...@@ -89,7 +95,7 @@ class TALParser(XMLParser):
key = "xmlns:" + prefix key = "xmlns:" + prefix
else: else:
key = "xmlns" 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") item = (key, uri, "xmlns")
else: else:
item = (key, uri) item = (key, uri)
...@@ -109,6 +115,8 @@ class TALParser(XMLParser): ...@@ -109,6 +115,8 @@ class TALParser(XMLParser):
ns = 'tal' ns = 'tal'
elif uri == ZOPE_METAL_NS: elif uri == ZOPE_METAL_NS:
ns = 'metal' ns = 'metal'
elif uri == ZOPE_I18N_NS:
ns = 'i18n'
return (prefixed, name, ns) return (prefixed, name, ns)
return (name, name, None) return (name, name, None)
......
##############################################################################
#
# 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.0 (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.
#
##############################################################################
"""Translation context object for the TALInterpreter's I18N support.
The translation context provides a container for the information
needed to perform translation of a marked string from a page template.
$Id: TranslationContext.py,v 1.2 2002/09/18 15:12:48 efge Exp $
"""
DEFAULT_DOMAIN = "default"
class TranslationContext:
"""Information about the I18N settings of a TAL processor."""
def __init__(self, parent=None, domain=None, target=None, source=None):
if parent:
if not domain:
domain = parent.domain
if not target:
target = parent.target
if not source:
source = parent.source
elif domain is None:
domain = DEFAULT_DOMAIN
self.parent = parent
self.domain = domain
self.target = target
self.source = source
...@@ -50,7 +50,7 @@ class XMLParser: ...@@ -50,7 +50,7 @@ class XMLParser:
try: try:
self.parser.ordered_attributes = self.ordered_attributes self.parser.ordered_attributes = self.ordered_attributes
except AttributeError: except AttributeError:
zLOG.LOG("TAL.XMLParser", zLOG.INFO, zLOG.LOG("TAL.XMLParser", zLOG.INFO,
"Can't set ordered_attributes") "Can't set ordered_attributes")
self.ordered_attributes = 0 self.ordered_attributes = 0
for name in self.handler_names: for name in self.handler_names:
......
...@@ -9,11 +9,31 @@ ...@@ -9,11 +9,31 @@
# 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.
# #
############################################################################## ##############################################################################
""" """
Driver program to test METAL and TAL implementation. Driver program to test METAL and TAL implementation.
Usage: driver.py [options] [file]
Options:
-h / --help
Print this message and exit.
-H / --html
-x / --xml
Explicitly choose HTML or XML input. The default is to automatically
select based on the file extension. These options are mutually
exclusive.
-l
Lenient structure insertion.
-m
Macro expansion only
-s
Print intermediate opcodes only
-t
Leave TAL/METAL attributes in output
-i
Leave I18N substitution strings un-interpolated.
""" """
import os import os
...@@ -26,68 +46,131 @@ if __name__ == "__main__": ...@@ -26,68 +46,131 @@ if __name__ == "__main__":
# Import local classes # Import local classes
import TALDefs import TALDefs
import DummyEngine from DummyEngine import DummyEngine
from DummyEngine import DummyTranslationService
FILE = "tests/input/test01.xml" FILE = "tests/input/test01.xml"
class TestTranslations(DummyTranslationService):
def translate(self, domain, msgid, mapping=None, context=None,
target_language=None):
if msgid == 'timefmt':
return '%(minutes)s minutes after %(hours)s %(ampm)s' % mapping
elif msgid == 'jobnum':
return '%(jobnum)s is the JOB NUMBER' % mapping
elif msgid == 'verify':
s = 'Your contact email address is recorded as %(email)s'
return s % mapping
elif msgid == 'mailto:${request/submitter}':
return 'mailto:bperson@dom.ain'
elif msgid == 'origin':
return '%(name)s was born in %(country)s' % mapping
return DummyTranslationService.translate(self, domain, msgid,
mapping, context,
target_language)
class TestEngine(DummyEngine):
def __init__(self, macros=None):
DummyEngine.__init__(self, macros)
self.translationService = TestTranslations()
def evaluatePathOrVar(self, expr):
if expr == 'here/currentTime':
return {'hours' : 6,
'minutes': 59,
'ampm' : 'PM',
}
elif expr == 'context/@@object_name':
return '7'
elif expr == 'request/submitter':
return 'aperson@dom.ain'
return DummyEngine.evaluatePathOrVar(self, expr)
# This is a disgusting hack so that we can use engines that actually know
# something about certain object paths. TimeEngine knows about
# here/currentTime.
ENGINES = {'test23.html': TestEngine,
'test24.html': TestEngine,
'test26.html': TestEngine,
'test27.html': TestEngine,
'test28.html': TestEngine,
'test29.html': TestEngine,
'test30.html': TestEngine,
'test31.html': TestEngine,
'test32.html': TestEngine,
}
def usage(code, msg=''):
# Python 2.1 required
print >> sys.stderr, __doc__
if msg:
print >> sys.stderr, msg
sys.exit(code)
def main(): def main():
versionTest = 1
macros = 0 macros = 0
mode = None mode = None
showcode = 0 showcode = 0
showtal = -1 showtal = -1
strictinsert = 1 strictinsert = 1
i18nInterpolate = 1
try: try:
opts, args = getopt.getopt(sys.argv[1:], "hxlmnst") opts, args = getopt.getopt(sys.argv[1:], "hHxlmsti",
['help', 'html', 'xml'])
except getopt.error, msg: except getopt.error, msg:
sys.stderr.write("\n%s\n" % str(msg)) usage(2, msg)
sys.stderr.write( for opt, arg in opts:
"usage: driver.py [-h|-x] [-l] [-m] [-n] [-s] [-t] [file]\n") if opt in ('-h', '--help'):
sys.stderr.write("-h/-x -- HTML/XML input (default auto)\n") usage(0)
sys.stderr.write("-l -- lenient structure insertion\n") if opt in ('-H', '--html'):
sys.stderr.write("-m -- macro expansion only\n") if mode == 'xml':
sys.stderr.write("-n -- turn off the Python 1.5.2 test\n") usage(1, '--html and --xml are mutually exclusive')
sys.stderr.write("-s -- print intermediate code\n")
sys.stderr.write("-t -- leave tal/metal attributes in output\n")
sys.exit(2)
for o, a in opts:
if o == '-h':
mode = "html" mode = "html"
if o == '-l': if opt == '-l':
strictinsert = 0 strictinsert = 0
if o == '-m': if opt == '-m':
macros = 1 macros = 1
if o == '-n': if opt == '-n':
versionTest = 0 versionTest = 0
if o == '-x': if opt in ('-x', '--xml'):
if mode == 'html':
usage(1, '--html and --xml are mutually exclusive')
mode = "xml" mode = "xml"
if o == '-s': if opt == '-s':
showcode = 1 showcode = 1
if o == '-t': if opt == '-t':
showtal = 1 showtal = 1
if not versionTest: if opt == '-i':
if sys.version[:5] != "1.5.2": i18nInterpolate = 0
sys.stderr.write(
"Use Python 1.5.2 only; use -n to disable this test\n")
sys.exit(2)
if args: if args:
file = args[0] file = args[0]
else: else:
file = FILE file = FILE
it = compilefile(file, mode) it = compilefile(file, mode)
if showcode: showit(it) if showcode:
else: interpretit(it, tal=(not macros), showtal=showtal, showit(it)
strictinsert=strictinsert) else:
# See if we need a special engine for this test
engine = None
engineClass = ENGINES.get(os.path.basename(file))
if engineClass is not None:
engine = engineClass(macros)
interpretit(it, engine=engine,
tal=(not macros), showtal=showtal,
strictinsert=strictinsert,
i18nInterpolate=i18nInterpolate)
def interpretit(it, engine=None, stream=None, tal=1, showtal=-1, def interpretit(it, engine=None, stream=None, tal=1, showtal=-1,
strictinsert=1): strictinsert=1, i18nInterpolate=1):
from TALInterpreter import TALInterpreter from TALInterpreter import TALInterpreter
program, macros = it program, macros = it
assert TALDefs.isCurrentVersion(program) assert TALDefs.isCurrentVersion(program)
if engine is None: if engine is None:
engine = DummyEngine.DummyEngine(macros) engine = DummyEngine(macros)
TALInterpreter(program, macros, engine, stream, wrap=0, TALInterpreter(program, macros, engine, stream, wrap=0,
tal=tal, showtal=showtal, strictinsert=strictinsert)() tal=tal, showtal=showtal, strictinsert=strictinsert,
i18nInterpolate=i18nInterpolate)()
def compilefile(file, mode=None): def compilefile(file, mode=None):
assert mode in ("html", "xml", None) assert mode in ("html", "xml", None)
......
...@@ -71,8 +71,8 @@ def main(): ...@@ -71,8 +71,8 @@ def main():
htmlargs.sort() htmlargs.sort()
args = xmlargs + htmlargs args = xmlargs + htmlargs
if not args: if not args:
sys.stderr.write("No tests found -- please supply filenames\n") sys.stderr.write("No tests found -- please supply filenames\n")
sys.exit(1) sys.exit(1)
errors = 0 errors = 0
for arg in args: for arg in args:
locopts = [] locopts = []
......
...@@ -17,6 +17,10 @@ class FileTestCase(unittest.TestCase): ...@@ -17,6 +17,10 @@ class FileTestCase(unittest.TestCase):
self.__dir = dir self.__dir = dir
unittest.TestCase.__init__(self) unittest.TestCase.__init__(self)
def shortDescription(self):
return os.path.join("...", "TAL", "tests", "input",
os.path.basename(self.__file))
def runTest(self): def runTest(self):
basename = os.path.basename(self.__file) basename = os.path.basename(self.__file)
#sys.stdout.write(basename + " ") #sys.stdout.write(basename + " ")
......
This diff is collapsed.
...@@ -68,6 +68,24 @@ class OutputPresentationTestCase(TestCaseBase): ...@@ -68,6 +68,24 @@ class OutputPresentationTestCase(TestCaseBase):
interp() interp()
self.assertEqual(sio.getvalue(), EXPECTED) self.assertEqual(sio.getvalue(), EXPECTED)
def check_unicode_content(self):
INPUT = """<p tal:content="python:u'dj-vu'">para</p>"""
EXPECTED = u"""<p>dj-vu</p>""" "\n"
program, macros = self._compile(INPUT)
sio = StringIO()
interp = TALInterpreter(program, {}, DummyEngine(), sio, wrap=60)
interp()
self.assertEqual(sio.getvalue(), EXPECTED)
def check_unicode_structure(self):
INPUT = """<p tal:replace="structure python:u'dj-vu'">para</p>"""
EXPECTED = u"""dj-vu""" "\n"
program, macros = self._compile(INPUT)
sio = StringIO()
interp = TALInterpreter(program, {}, DummyEngine(), sio, wrap=60)
interp()
self.assertEqual(sio.getvalue(), EXPECTED)
def test_suite(): def test_suite():
suite = unittest.TestSuite() suite = unittest.TestSuite()
......
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