Commit 82471624 authored by Philipp von Weitershausen's avatar Philipp von Weitershausen

Merge ajung-zpt-end-game branch.

parents 997aef4e d8a6bbd0
...@@ -18,6 +18,18 @@ Zope Changes ...@@ -18,6 +18,18 @@ Zope Changes
Restructuring Restructuring
- Products.PageTemplates now uses the Zope 3 ZPT implementation
in zope.pagetemplate.
- The TAL package has been deprecated in favour of the TAL
engine from zope.tal.
- Products.PageTemplates.TALES has been deprecated in favour of
the TALES engine from zope.tales.
- ZTUtils.Iterator has been deprecated in favour of the TALES
iterator implementation in zope.tales.tales.
- ZCatalog: removed manage_deleteIndex(), manage_delColumns() - ZCatalog: removed manage_deleteIndex(), manage_delColumns()
which were deprecated since Zope 2.4 which were deprecated since Zope 2.4
......
...@@ -10,32 +10,16 @@ ...@@ -10,32 +10,16 @@
# FOR A PARTICULAR PURPOSE # FOR A PARTICULAR PURPOSE
# #
############################################################################## ##############################################################################
"""Defer and Lazy expression handler """Lazy expression handler
defer expressions can be usesd for a design pattern called deferred evaluation. A lazy expressions is implemented similarly to the defer expression
but has a different result. While a defer expression is evaluated
Example: every time it is used according to its context a lazy expression is
evaluted only the first time it is used. Lazy expression are known
<div tal:define="xis defer:string:x is $x"> under the name lazy initialization of variables, too. A common use
<p tal:repeat="x python:range(3)" case for a lazy expression is a lazy binding of a costly expression.
tal:content="xis"></p> While one could call an expression only when it's required it makes
</div> sense to define it only one time when it could be used multiple times.
Output:
<div>
<p>x is 0</p>
<p>x is 1</p>
<p>x is 2</p>
</div>
A lazy expressions is implemented in a similar way but has a different result. While
a defer expression is evaluated every time it is used according to its context a lazy
expression is evaluted only the first time it is used. Lazy expression are known
under the name lazy initialization of variables, too.
A common use case for a lazy expression is a lazy binding of a costly expression.
While one could call an expression only when it's required it makes sense to define
it only one time when it could be used multiple times.
Example Example
...@@ -45,38 +29,11 @@ Example ...@@ -45,38 +29,11 @@ Example
<div tal:condition"python: not (foo or bar)">...</div> <div tal:condition"python: not (foo or bar)">...</div>
</div> </div>
""" """
from zope.tales.expressions import DeferWrapper, DeferExpr
_marker = object() _marker = object()
# defer expression # TODO These should really be integrated into the Zope 3 ZPT
# implementation (zope.tales)
class DeferWrapper:
"""Wrapper for defer: expression
"""
def __init__(self, expr, econtext):
self._expr = expr
self._econtext = econtext
def __str__(self):
return str(self())
def __call__(self):
return self._expr(self._econtext)
class DeferExpr:
"""defer: expression handler for deferred evaluation of the context
"""
def __init__(self, name, expr, compiler):
self._s = expr = expr.lstrip()
self._c = compiler.compile(expr)
def __call__(self, econtext):
return DeferWrapper(self._c, econtext)
def __repr__(self):
return 'defer:%s' % `self._s`
# lazy expression
class LazyWrapper(DeferWrapper): class LazyWrapper(DeferWrapper):
"""Wrapper for lazy: expression """Wrapper for lazy: expression
...@@ -99,4 +56,3 @@ class LazyExpr(DeferExpr): ...@@ -99,4 +56,3 @@ class LazyExpr(DeferExpr):
def __repr__(self): def __repr__(self):
return 'lazy:%s' % `self._s` return 'lazy:%s' % `self._s`
...@@ -15,12 +15,11 @@ ...@@ -15,12 +15,11 @@
$Id$ $Id$
""" """
import re import re
import Products.Five.i18n import Products.Five.i18n
from DocumentTemplate.DT_Util import ustr from DocumentTemplate.DT_Util import ustr
from TAL.TALDefs import NAME_RE from zope.tal.taldefs import NAME_RE
class DummyTranslationService: class DummyTranslationService:
"""Translation service that doesn't know anything about translation.""" """Translation service that doesn't know anything about translation."""
...@@ -30,13 +29,15 @@ class DummyTranslationService: ...@@ -30,13 +29,15 @@ class DummyTranslationService:
return ustr(mapping[m.group(m.lastindex)]) return ustr(mapping[m.group(m.lastindex)])
cre = re.compile(r'\$(?:(%s)|\{(%s)\})' % (NAME_RE, NAME_RE)) cre = re.compile(r'\$(?:(%s)|\{(%s)\})' % (NAME_RE, NAME_RE))
return cre.sub(repl, default or msgid) return cre.sub(repl, default or msgid)
# XXX Not all of Zope2.I18n.ITranslationService is implemented.
# #
# As of Five 1.1, we're by default using Zope 3 Message Catalogs for # As of Five 1.1, we're by default using Zope 3 Message Catalogs for
# translation, but we allow fallback translation services such as PTS # translation, but we allow fallback translation services such as PTS
# and Localizer # and Localizer
# #
# TODO We should really deprecate Zope2-style translation service and
# only support Zope3-style i18n in the future.
#
Products.Five.i18n._fallback_translation_service = DummyTranslationService() Products.Five.i18n._fallback_translation_service = DummyTranslationService()
fiveTranslationService = Products.Five.i18n.FiveTranslationService() fiveTranslationService = Products.Five.i18n.FiveTranslationService()
......
...@@ -12,65 +12,47 @@ ...@@ -12,65 +12,47 @@
############################################################################## ##############################################################################
"""Page Template module """Page Template module
HTML- and XML-based template objects using TAL, TALES, and METAL. $Id$
""" """
import sys
import ExtensionClass
import zope.pagetemplate.pagetemplate
from zope.pagetemplate.pagetemplate import _error_start, PTRuntimeError
from zope.pagetemplate.pagetemplate import PageTemplateTracebackSupplement
from zope.tales.expressions import SimpleModuleImporter
from Products.PageTemplates.Expressions import getEngine
__version__='$Revision: 1.31 $'[11:-2] ##############################################################################
# BBB 2005/05/01 -- to be removed after 12 months
import sys, types _ModuleImporter = SimpleModuleImporter
ModuleImporter = SimpleModuleImporter()
from TAL.TALParser import TALParser import zope.deprecation
from TAL.HTMLTALParser import HTMLTALParser zope.deprecation.deprecated(
from TAL.TALGenerator import TALGenerator ('ModuleImporter', '_ModuleImporter'),
# Do not use cStringIO here! It's not unicode aware. :( "Zope 2 uses the Zope 3 ZPT engine now. ModuleImporter has moved "
from TAL.TALInterpreter import TALInterpreter, FasterStringIO "to zope.pagetemplate.pagetemplate.SimpleModuleImporter (this is a "
from Expressions import getEngine "class, not an instance)."
from ExtensionClass import Base )
from ComputedAttribute import ComputedAttribute zope.deprecation.deprecated(
('PTRuntimeError', 'PageTemplateTracebackSupplement'),
"Zope 2 uses the Zope 3 ZPT engine now. The object you're importing "
class PageTemplate(Base): "has moved to zope.pagetemplate.pagetemplate. This reference will "
"Page Templates using TAL, TALES, and METAL" "be gone in Zope 2.12.",
)
content_type = 'text/html' ##############################################################################
expand = 0
_v_errors = ()
_v_warnings = ()
_v_program = None
_v_macros = None
_v_cooked = 0
id = '(unknown)'
_text = ''
_error_start = '<!-- Page Template Diagnostics'
def StringIO(self):
# Third-party products wishing to provide a full Unicode-aware
# StringIO can do so by monkey-patching this method.
return FasterStringIO()
def macros(self): class PageTemplate(ExtensionClass.Base,
return self.pt_macros() zope.pagetemplate.pagetemplate.PageTemplate):
macros = ComputedAttribute(macros, 1)
def pt_edit(self, text, content_type): def pt_getEngine(self):
if content_type: return getEngine()
self.content_type = str(content_type)
if hasattr(text, 'read'):
text = text.read()
charset = getattr(self, 'management_page_charset', None)
if charset and type(text) == types.StringType:
try:
unicode(text,'us-ascii')
except UnicodeDecodeError:
text = unicode(text, charset)
self.write(text)
def pt_getContext(self): def pt_getContext(self):
c = {'template': self, c = {'template': self,
'options': {}, 'options': {},
'nothing': None, 'nothing': None,
'request': None, 'request': None,
'modules': ModuleImporter, 'modules': SimpleModuleImporter(),
} }
parent = getattr(self, 'aq_parent', None) parent = getattr(self, 'aq_parent', None)
if parent is not None: if parent is not None:
...@@ -83,73 +65,43 @@ class PageTemplate(Base): ...@@ -83,73 +65,43 @@ class PageTemplate(Base):
c['root'] = self c['root'] = self
return c return c
def pt_render(self, source=0, extra_context={}): @property
"""Render this Page Template""" def macros(self):
if not self._v_cooked: return self.pt_macros()
self._cook()
__traceback_supplement__ = (PageTemplateTracebackSupplement, self)
# sub classes may override this to do additional stuff for macro access
def pt_macros(self):
self._cook_check()
if self._v_errors: if self._v_errors:
e = str(self._v_errors) __traceback_supplement__ = (PageTemplateTracebackSupplement, self, {})
raise PTRuntimeError, ( raise PTRuntimeError, (
'Page Template %s has errors: %s' % (self.id, e)) 'Page Template %s has errors: %s' % (
output = self.StringIO() self.id, self._v_errors
c = self.pt_getContext() ))
c.update(extra_context) return self._v_macros
TALInterpreter(self._v_program, self._v_macros, # these methods are reimplemented or duplicated here because of
getEngine().getContext(c), # different call signatures in the Zope 2 world
output,
tal=not source, strictinsert=0)()
return output.getvalue()
def __call__(self, *args, **kwargs): def pt_render(self, source=False, extra_context={}):
if not kwargs.has_key('args'): c = self.pt_getContext()
kwargs['args'] = args c.update(extra_context)
return self.pt_render(extra_context={'options': kwargs}) return super(PageTemplate, self).pt_render(c, source=source)
def pt_errors(self): def pt_errors(self, namespace={}):
if not self._v_cooked: self._cook_check()
self._cook()
err = self._v_errors err = self._v_errors
if err: if err:
return err return err
if not self.expand: return
try: try:
self.pt_render(source=1) self.pt_render(source=True, extra_context=namespace)
except: except:
return ('Macro expansion failed', '%s: %s' % sys.exc_info()[:2]) return ('Macro expansion failed', '%s: %s' % sys.exc_info()[:2])
def pt_warnings(self): def __call__(self, *args, **kwargs):
if not self._v_cooked: if not kwargs.has_key('args'):
self._cook() kwargs['args'] = args
return self._v_warnings return self.pt_render(extra_context={'options': kwargs})
def pt_macros(self):
if not self._v_cooked:
self._cook()
if self._v_errors:
__traceback_supplement__ = (PageTemplateTracebackSupplement, self)
raise PTRuntimeError, (
'Page Template %s has errors: %s' % (
self.id,
self._v_errors
))
return self._v_macros
def pt_source_file(self):
return None # Unknown.
def write(self, text):
assert type(text) in types.StringTypes
if text[:len(self._error_start)] == self._error_start:
errend = text.find('-->')
if errend >= 0:
text = text[errend + 4:]
if self._text != text:
self._text = text
self._cook()
def read(self): def read(self):
self._cook_check() self._cook_check()
...@@ -157,71 +109,21 @@ class PageTemplate(Base): ...@@ -157,71 +109,21 @@ class PageTemplate(Base):
if not self.expand: if not self.expand:
return self._text return self._text
try: try:
return self.pt_render(source=1) return self.pt_render(source=True)
except: except:
return ('%s\n Macro expansion failed\n %s\n-->\n%s' % return ('%s\n Macro expansion failed\n %s\n-->\n%s' %
(self._error_start, "%s: %s" % sys.exc_info()[:2], (_error_start, "%s: %s" % sys.exc_info()[:2],
self._text) ) self._text) )
return ('%s\n %s\n-->\n%s' % (self._error_start, return ('%s\n %s\n-->\n%s' % (_error_start,
'\n '.join(self._v_errors), '\n '.join(self._v_errors),
self._text)) self._text))
def _cook_check(self): # convenience method for the ZMI which allows to explicitly
if not self._v_cooked: # specify the HTMLness of a template. The old Zope 2
self._cook() # implementation had this as well, but arguably on the wrong class
# (this should be a ZopePageTemplate thing if at all)
def _cook(self):
"""Compile the TAL and METAL statments.
Cooking must not fail due to compilation errors in templates.
"""
source_file = self.pt_source_file()
if self.html():
gen = TALGenerator(getEngine(), xml=0, source_file=source_file)
parser = HTMLTALParser(gen)
else:
gen = TALGenerator(getEngine(), source_file=source_file)
parser = TALParser(gen)
self._v_errors = ()
try:
parser.parseString(self._text)
self._v_program, self._v_macros = parser.getCode()
except:
self._v_errors = ["Compilation failed",
"%s: %s" % sys.exc_info()[:2]]
self._v_warnings = parser.getWarnings()
self._v_cooked = 1
def html(self): def html(self):
if not hasattr(getattr(self, 'aq_base', self), 'is_html'): if not hasattr(getattr(self, 'aq_base', self), 'is_html'):
return self.content_type == 'text/html' return self.content_type == 'text/html'
return self.is_html return self.is_html
class _ModuleImporter:
def __getitem__(self, module):
mod = __import__(module)
path = module.split('.')
for name in path[1:]:
mod = getattr(mod, name)
return mod
ModuleImporter = _ModuleImporter()
class PTRuntimeError(RuntimeError):
'''The Page Template has template errors that prevent it from rendering.'''
pass
class PageTemplateTracebackSupplement:
#__implements__ = ITracebackSupplement
def __init__(self, pt):
self.object = pt
w = pt.pt_warnings()
e = pt.pt_errors()
if e:
w = list(w) + list(e)
self.warnings = w
...@@ -10,32 +10,35 @@ ...@@ -10,32 +10,35 @@
# FOR A PARTICULAR PURPOSE # FOR A PARTICULAR PURPOSE
# #
############################################################################## ##############################################################################
"""Filesystem Page Template module
Zope object encapsulating a Page Template from the filesystem. import os
"""
__version__ = '$Revision: 1.30 $'[11:-2]
import os, AccessControl
from logging import getLogger from logging import getLogger
from Globals import package_home, DevelopmentMode
import AccessControl
from Globals import package_home, InitializeClass, DevelopmentMode
from App.config import getConfiguration
from Acquisition import aq_parent, aq_inner
from ComputedAttribute import ComputedAttribute
from OFS.SimpleItem import SimpleItem
from OFS.Traversable import Traversable
from Shared.DC.Scripts.Script import Script from Shared.DC.Scripts.Script import Script
from Shared.DC.Scripts.Signature import FuncCode from Shared.DC.Scripts.Signature import FuncCode
from AccessControl import getSecurityManager from Products.PageTemplates.Expressions import SecureModuleImporter
from OFS.Traversable import Traversable from Products.PageTemplates.PageTemplate import PageTemplate
from PageTemplate import PageTemplate
from Expressions import SecureModuleImporter
from ComputedAttribute import ComputedAttribute
from Acquisition import aq_parent, aq_inner
from App.config import getConfiguration
from OFS.SimpleItem import Item_w__name__
from zope.contenttype import guess_content_type
from zope.pagetemplate.pagetemplatefile import sniff_type
LOG = getLogger('PageTemplateFile') LOG = getLogger('PageTemplateFile')
class PageTemplateFile(Item_w__name__, Script, PageTemplate, Traversable): def guess_type(filename, text):
"Zope wrapper for filesystem Page Template using TAL, TALES, and METAL" content_type, dummy = guess_content_type(filename, text)
if content_type in ('text/html', 'text/xml'):
return content_type
return sniff_type(text) or 'text/html'
class PageTemplateFile(SimpleItem, Script, PageTemplate, Traversable):
"""Zope 2 implementation of a PageTemplate loaded from a file."""
meta_type = 'Page Template (File)' meta_type = 'Page Template (File)'
...@@ -53,28 +56,34 @@ class PageTemplateFile(Item_w__name__, Script, PageTemplate, Traversable): ...@@ -53,28 +56,34 @@ class PageTemplateFile(Item_w__name__, Script, PageTemplate, Traversable):
security.declareProtected('View management screens', security.declareProtected('View management screens',
'read', 'document_src') 'read', 'document_src')
_default_bindings = {'name_subpath': 'traverse_subpath'}
def __init__(self, filename, _prefix=None, **kw): def __init__(self, filename, _prefix=None, **kw):
self.ZBindings_edit(self._default_bindings) name = None
if _prefix is None: if kw.has_key('__name__'):
_prefix = getConfiguration().softwarehome name = kw['__name__']
elif not isinstance(_prefix, str): del kw['__name__']
_prefix = package_home(_prefix)
name = kw.get('__name__')
basepath, ext = os.path.splitext(filename) basepath, ext = os.path.splitext(filename)
if name: if name:
self._need__name__ = 0 self.id = self.__name__ = name
self.__name__ = name
else: else:
self.__name__ = os.path.basename(basepath) self.id = self.__name__ = os.path.basename(basepath)
if _prefix:
if isinstance(_prefix, str):
filename = os.path.join(_prefix, filename)
else:
filename = os.path.join(package_home(_prefix), filename)
if not ext: if not ext:
# XXX This is pretty bogus, but can't be removed since
# it's been released this way.
filename = filename + '.zpt' filename = filename + '.zpt'
self.filename = os.path.join(_prefix, filename)
def getId(self): self.filename = filename
"""return the ID of this object"""
return self.__name__ content = open(filename).read()
self.pt_edit( content, guess_type(filename, content))
def pt_getContext(self): def pt_getContext(self):
root = self.getPhysicalRoot() root = self.getPhysicalRoot()
...@@ -106,10 +115,13 @@ class PageTemplateFile(Item_w__name__, Script, PageTemplate, Traversable): ...@@ -106,10 +115,13 @@ class PageTemplateFile(Item_w__name__, Script, PageTemplate, Traversable):
pass pass
# Execute the template in a new security context. # Execute the template in a new security context.
security = getSecurityManager() security = AccessControl.getSecurityManager()
bound_names['user'] = security.getUser() bound_names['user'] = security.getUser()
security.addContext(self) security.addContext(self)
try: try:
context = self.pt_getContext()
context.update(bound_names)
return self.pt_render(extra_context=bound_names) return self.pt_render(extra_context=bound_names)
finally: finally:
security.removeContext(self) security.removeContext(self)
...@@ -187,6 +199,7 @@ class PageTemplateFile(Item_w__name__, Script, PageTemplate, Traversable): ...@@ -187,6 +199,7 @@ class PageTemplateFile(Item_w__name__, Script, PageTemplate, Traversable):
raise StorageError, ("Instance of AntiPersistent class %s " raise StorageError, ("Instance of AntiPersistent class %s "
"cannot be stored." % self.__class__.__name__) "cannot be stored." % self.__class__.__name__)
InitializeClass(PageTemplateFile)
XML_PREFIXES = [ XML_PREFIXES = [
"<?xml", # ascii, utf-8 "<?xml", # ascii, utf-8
......
...@@ -10,37 +10,16 @@ ...@@ -10,37 +10,16 @@
# FOR A PARTICULAR PURPOSE # FOR A PARTICULAR PURPOSE
# #
############################################################################## ##############################################################################
"""Path Iterator """Path Iterator
A TALES Iterator with the ability to use first() and last() on BBB 2005/05/01 -- to be removed after 12 months
subpaths of elements.
"""
__version__='$Revision: 1.4 $'[11:-2]
import TALES $Id$
from Expressions import restrictedTraverse, Undefs, getSecurityManager """
import zope.deferredimport
class Iterator(TALES.Iterator): zope.deferredimport.deprecated(
def __bobo_traverse__(self, REQUEST, name): "It has been renamed to PathIterator and moved to the "
if name in ('first', 'last'): "Products.PageTemplates.Expressions module. This reference will be "
path = REQUEST['TraversalRequestNameStack'] "gone in Zope 2.12.",
names = list(path) PathIterator = "Products.PageTemplates.Expressions:PathIterator"
names.reverse() )
path[:] = [tuple(names)]
return getattr(self, name)
def same_part(self, name, ob1, ob2):
if name is None:
return ob1 == ob2
if isinstance(name, type('')):
name = name.split('/')
name = filter(None, name)
securityManager = getSecurityManager()
try:
ob1 = restrictedTraverse(ob1, name, securityManager)
ob2 = restrictedTraverse(ob2, name, securityManager)
except Undefs:
return 0
return ob1 == ob2
...@@ -10,77 +10,10 @@ ...@@ -10,77 +10,10 @@
# FOR A PARTICULAR PURPOSE # FOR A PARTICULAR PURPOSE
# #
############################################################################## ##############################################################################
"""Generic Python Expression Handler """Generic Python Expression Handler
"""
__version__='$Revision: 1.13 $'[11:-2]
from TALES import CompilerError
from sys import exc_info
from DeferExpr import DeferWrapper
class getSecurityManager:
'''Null security manager'''
def validate(self, *args, **kwargs):
return 1
addContext = removeContext = validate
class PythonExpr:
def __init__(self, name, expr, engine):
self.expr = expr = expr.strip().replace('\n', ' ')
try:
d = {}
exec 'def f():\n return %s\n' % expr.strip() in d
self._f = d['f']
except:
raise CompilerError, ('Python expression error:\n'
'%s: %s') % exc_info()[:2]
self._get_used_names()
def _get_used_names(self):
self._f_varnames = vnames = []
for vname in self._f.func_code.co_names:
if vname[0] not in '$_':
vnames.append(vname)
def _bind_used_names(self, econtext, _marker=[]):
# Bind template variables
names = {'CONTEXTS': econtext.contexts}
vars = econtext.vars
getType = econtext.getCompiler().getTypes().get
for vname in self._f_varnames:
val = vars.get(vname, _marker)
if val is _marker:
has = val = getType(vname)
if has:
val = ExprTypeProxy(vname, val, econtext)
names[vname] = val
else:
names[vname] = val
for key, val in names.items():
if isinstance(val, DeferWrapper):
names[key] = val()
return names
def __call__(self, econtext):
__traceback_info__ = self.expr
f = self._f
f.func_globals.update(self._bind_used_names(econtext))
return f()
def __str__(self):
return 'Python expression "%s"' % self.expr
def __repr__(self):
return '<PythonExpr %s>' % self.expr
class ExprTypeProxy:
'''Class that proxies access to an expression type handler'''
def __init__(self, name, handler, econtext):
self._name = name
self._handler = handler
self._econtext = econtext
def __call__(self, text):
return self._handler(self._name, text,
self._econtext.getCompiler())(self._econtext)
$Id$
"""
# BBB 2005/05/01 -- remove after 12 months
import zope.deprecation
zope.deprecation.moved("zope.tales.pythonexpr", "2.12")
See <a href="http://dev.zope.org/Wikis/DevSite/Projects/ZPT">the
ZPT project Wiki</a> for more information about Page Templates, or
<a href="http://www.zope.org/Members/4am/ZPT">the download page</a>
for installation instructions and the most recent version of the software.
This Product requires the TAL and ZTUtils packages to be installed in
your Python path (not Products). See the links above for more information.
...@@ -12,40 +12,15 @@ ...@@ -12,40 +12,15 @@
############################################################################## ##############################################################################
"""TALES """TALES
An implementation of a generic TALES engine BBB 2005/05/01 -- to be removed after 12 months
"""
__version__='$Revision: 1.39 $'[11:-2] $Id$
"""
from zope.tales.tests.simpleexpr import SimpleExpr
from zope.tales.tales import ExpressionEngine as Engine
from zope.tales.tales import _default as Default
import re, sys, ZTUtils
from weakref import ref
from MultiMapping import MultiMapping from MultiMapping import MultiMapping
from DocumentTemplate.DT_Util import ustr
from GlobalTranslationService import getGlobalTranslationService
from zExceptions import Unauthorized
StringType = type('')
NAME_RE = r"[a-zA-Z][a-zA-Z0-9_]*"
_parse_expr = re.compile(r"(%s):" % NAME_RE).match
_valid_name = re.compile('%s$' % NAME_RE).match
class TALESError(Exception):
"""Error during TALES expression evaluation"""
class Undefined(TALESError):
'''Exception raised on traversal of an undefined path'''
class RegistrationError(Exception):
'''TALES Type Registration Error'''
class CompilerError(Exception):
'''TALES Compiler Error'''
class Default:
'''Retain Default'''
Default = Default()
class SafeMapping(MultiMapping): class SafeMapping(MultiMapping):
'''Mapping with security declarations and limited method exposure. '''Mapping with security declarations and limited method exposure.
...@@ -60,241 +35,5 @@ class SafeMapping(MultiMapping): ...@@ -60,241 +35,5 @@ class SafeMapping(MultiMapping):
_push = MultiMapping.push _push = MultiMapping.push
_pop = MultiMapping.pop _pop = MultiMapping.pop
import zope.deprecation
class Iterator(ZTUtils.Iterator): zope.deprecation.moved("zope.tales.tales", "2.12")
def __init__(self, name, seq, context):
ZTUtils.Iterator.__init__(self, seq)
self.name = name
self._context_ref = ref(context)
def next(self):
if ZTUtils.Iterator.next(self):
context = self._context_ref()
if context is not None:
context.setLocal(self.name, self.item)
return 1
return 0
class ErrorInfo:
"""Information about an exception passed to an on-error handler."""
__allow_access_to_unprotected_subobjects__ = 1
def __init__(self, err, position=(None, None)):
if isinstance(err, Exception):
self.type = err.__class__
self.value = err
else:
self.type = err
self.value = None
self.lineno = position[0]
self.offset = position[1]
class Engine:
'''Expression Engine
An instance of this class keeps a mutable collection of expression
type handlers. It can compile expression strings by delegating to
these handlers. It can provide an expression Context, which is
capable of holding state and evaluating compiled expressions.
'''
Iterator = Iterator
def __init__(self, Iterator=None):
self.types = {}
if Iterator is not None:
self.Iterator = Iterator
def registerType(self, name, handler):
if not _valid_name(name):
raise RegistrationError, 'Invalid Expression type "%s".' % name
types = self.types
if types.has_key(name):
raise RegistrationError, (
'Multiple registrations for Expression type "%s".' %
name)
types[name] = handler
def getTypes(self):
return self.types
def compile(self, expression):
m = _parse_expr(expression)
if m:
type = m.group(1)
expr = expression[m.end():]
else:
type = "standard"
expr = expression
try:
handler = self.types[type]
except KeyError:
raise CompilerError, (
'Unrecognized expression type "%s".' % type)
return handler(type, expr, self)
def getContext(self, contexts=None, **kwcontexts):
if contexts is not None:
if kwcontexts:
kwcontexts.update(contexts)
else:
kwcontexts = contexts
return Context(self, kwcontexts)
def getCompilerError(self):
return CompilerError
class Context:
'''Expression Context
An instance of this class holds context information that it can
use to evaluate compiled expressions.
'''
_context_class = SafeMapping
position = (None, None)
source_file = None
def __init__(self, compiler, contexts):
self._compiler = compiler
self.contexts = contexts
contexts['nothing'] = None
contexts['default'] = Default
self.repeat_vars = rv = {}
# Wrap this, as it is visible to restricted code
contexts['repeat'] = rep = self._context_class(rv)
contexts['loop'] = rep # alias
self.global_vars = gv = contexts.copy()
self.local_vars = lv = {}
self.vars = self._context_class(gv, lv)
# Keep track of what needs to be popped as each scope ends.
self._scope_stack = []
def getCompiler(self):
return self._compiler
def beginScope(self):
self._scope_stack.append([self.local_vars.copy()])
def endScope(self):
scope = self._scope_stack.pop()
self.local_vars = lv = scope[0]
v = self.vars
v._pop()
v._push(lv)
# Pop repeat variables, if any
i = len(scope) - 1
while i:
name, value = scope[i]
if value is None:
del self.repeat_vars[name]
else:
self.repeat_vars[name] = value
i = i - 1
def setLocal(self, name, value):
self.local_vars[name] = value
def setGlobal(self, name, value):
self.global_vars[name] = value
def setRepeat(self, name, expr):
expr = self.evaluate(expr)
if not expr:
return self._compiler.Iterator(name, (), self)
it = self._compiler.Iterator(name, expr, self)
old_value = self.repeat_vars.get(name)
self._scope_stack[-1].append((name, old_value))
self.repeat_vars[name] = it
return it
def evaluate(self, expression,
isinstance=isinstance, StringType=StringType):
if isinstance(expression, StringType):
expression = self._compiler.compile(expression)
__traceback_supplement__ = (
TALESTracebackSupplement, self, expression)
return expression(self)
evaluateValue = evaluate
evaluateBoolean = evaluate
def evaluateText(self, expr):
text = self.evaluate(expr)
if text is Default or text is None:
return text
return ustr(text)
def evaluateStructure(self, expr):
return self.evaluate(expr)
evaluateStructure = evaluate
def evaluateMacro(self, expr):
# XXX Should return None or a macro definition
return self.evaluate(expr)
evaluateMacro = evaluate
def createErrorInfo(self, err, position):
return ErrorInfo(err, position)
def getDefault(self):
return Default
def setSourceFile(self, source_file):
self.source_file = source_file
def setPosition(self, position):
self.position = position
def translate(self, domain, msgid, mapping=None,
context=None, target_language=None, default=None):
if context is None:
context = self.contexts.get('here')
return getGlobalTranslationService().translate(
domain, msgid, mapping=mapping,
context=context,
default=default,
target_language=target_language)
def getValue(self, name, default=None):
return self.vars.get(name, default)
class TALESTracebackSupplement:
"""Implementation of ITracebackSupplement"""
def __init__(self, context, expression):
self.context = context
self.source_url = context.source_file
self.line = context.position[0]
self.column = context.position[1]
self.expression = repr(expression)
def getInfo(self, as_html=0):
import pprint
from cgi import escape
data = self.context.contexts.copy()
try:
s = pprint.pformat(data)
except Unauthorized, e:
s = ' - %s: %s' % (getattr(e, '__class__', type(e)), e)
if as_html:
s = escape(s)
return s
if not as_html:
return ' - Names:\n %s' % s.replace('\n', '\n ')
else:
return '<b>Names:</b><pre>%s</pre>' % (escape(s))
class SimpleExpr:
'''Simple example of an expression type handler'''
def __init__(self, name, expr, engine):
self._name = name
self._expr = expr
def __call__(self, econtext):
return self._name, self._expr
def __repr__(self):
return '<SimpleExpr %s %s>' % (self._name, `self._expr`)
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""Old Zope-specific Python Expression Handler
Handler for Python expressions, using the pre-Python 2.1 restriction
machinery from PythonScripts.
"""
__version__='$Revision: 1.8 $'[11:-2]
from AccessControl import getSecurityManager
from Products.PythonScripts.Guarded import _marker, \
GuardedBlock, theGuard, safebin, WriteGuard, ReadGuard, UntupleFunction
from TALES import CompilerError
from PythonExpr import PythonExpr
class PythonExpr(PythonExpr):
def __init__(self, name, expr, engine):
self.expr = expr = expr.strip().replace('\n', ' ')
blk = GuardedBlock('def f():\n return \\\n %s\n' % expr)
if blk.errors:
raise CompilerError, ('Python expression error:\n%s' %
'\n'.join(blk.errors) )
guards = {'$guard': theGuard, '$write_guard': WriteGuard,
'$read_guard': ReadGuard, '__debug__': __debug__}
self._f = UntupleFunction(blk.t, guards, __builtins__=safebin)
self._get_used_names()
class _SecureModuleImporter:
__allow_access_to_unprotected_subobjects__ = 1
def __getitem__(self, module):
mod = safebin['__import__'](module)
path = module.split('.')
for name in path[1:]:
mod = getattr(mod, name)
return mod
from DocumentTemplate.DT_Util import TemplateDict, InstanceDict
def validate(accessed, container, name, value, dummy):
return getSecurityManager().validate(accessed, container, name, value)
def call_with_ns(f, ns, arg=1):
td = TemplateDict()
td.validate = validate
td.this = ns['here']
td._push(ns['request'])
td._push(InstanceDict(td.this, td))
td._push(ns)
try:
if arg==2:
return f(None, td)
else:
return f(td)
finally:
td._pop(3)
...@@ -10,20 +10,17 @@ ...@@ -10,20 +10,17 @@
# FOR A PARTICULAR PURPOSE # FOR A PARTICULAR PURPOSE
# #
############################################################################## ##############################################################################
"""Zope-specific Python Expression Handler """Zope-specific Python Expression Handler
Handler for Python expressions that uses the RestrictedPython package. Handler for Python expressions that uses the RestrictedPython package.
"""
__version__='$Revision: 1.11 $'[11:-2]
$Id$
"""
from AccessControl import safe_builtins from AccessControl import safe_builtins
from AccessControl.ZopeGuards import guarded_getattr, get_safe_globals from AccessControl.ZopeGuards import guarded_getattr, get_safe_globals
from RestrictedPython import compile_restricted_eval from RestrictedPython import compile_restricted_eval
from TALES import CompilerError from zope.tales.tales import CompilerError
from zope.tales.pythonexpr import PythonExpr
from PythonExpr import PythonExpr
class PythonExpr(PythonExpr): class PythonExpr(PythonExpr):
_globals = get_safe_globals() _globals = get_safe_globals()
...@@ -31,23 +28,23 @@ class PythonExpr(PythonExpr): ...@@ -31,23 +28,23 @@ class PythonExpr(PythonExpr):
_globals['__debug__' ] = __debug__ _globals['__debug__' ] = __debug__
def __init__(self, name, expr, engine): def __init__(self, name, expr, engine):
self.expr = expr = expr.strip().replace('\n', ' ') self.text = text = expr.strip().replace('\n', ' ')
code, err, warn, use = compile_restricted_eval(expr, str(self)) code, err, warn, use = compile_restricted_eval(text, str(self))
if err: if err:
raise CompilerError, ('Python expression error:\n%s' % raise engine.getCompilerError()('Python expression error:\n%s' %
'\n'.join(err) ) '\n'.join(err))
self._f_varnames = use.keys() self._varnames = use.keys()
self._code = code self._code = code
def __call__(self, econtext): def __call__(self, econtext):
__traceback_info__ = self.expr __traceback_info__ = self.text
code = self._code vars = self._bind_used_names(econtext, {})
g = self._bind_used_names(econtext) vars.update(self._globals)
g.update(self._globals) return eval(self._code, vars, {})
return eval(code, g, {})
class _SecureModuleImporter: class _SecureModuleImporter:
__allow_access_to_unprotected_subobjects__ = 1 __allow_access_to_unprotected_subobjects__ = True
def __getitem__(self, module): def __getitem__(self, module):
mod = safe_builtins['__import__'](module) mod = safe_builtins['__import__'](module)
path = module.split('.') path = module.split('.')
......
This diff is collapsed.
...@@ -10,14 +10,13 @@ ...@@ -10,14 +10,13 @@
# FOR A PARTICULAR PURPOSE # FOR A PARTICULAR PURPOSE
# #
############################################################################## ##############################################################################
__doc__='''Package wrapper for Page Templates """Package wrapper for Page Templates
This wrapper allows the Page Template modules to be segregated in a This wrapper allows the Page Template modules to be segregated in a
separate package. separate package.
$Id$''' $Id$
__version__='$$'[11:-2] """
# Placeholder for Zope Product data # Placeholder for Zope Product data
misc_ = {} misc_ = {}
......
<html>
<head>
<title tal:content="template/title">The title</title>
</head>
<body>
<h2><span tal:replace="here/title_or_id">content title or id</span>
<span tal:condition="template/title"
tal:replace="template/title">optional template title</span></h2>
This is Page Template <em tal:content="template/id">template id</em>.
</body>
</html>
<h1 tal:replace="structure here/manage_page_header">Header</h1>
<h2 tal:define="form_title string:Add Page Template"
tal:replace="structure here/manage_form_title">Form Title</h2>
<p class="form-help">
Page Templates allow you to use simple HTML or XML attributes to
create dynamic templates. You may choose to upload the template text
from a local file by typing the file name or using the <em>browse</em>
button.
</p>
<form action="manage_addPageTemplate" method="post"
enctype="multipart/form-data">
<table cellspacing="0" cellpadding="2" border="0">
<tr>
<td align="left" valign="top">
<div class="form-label">
Id
</div>
</td>
<td align="left" valign="top">
<input type="text" name="id" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-optional">
File
</div>
</td>
<td align="left" valign="top">
<input type="file" name="file" size="25" value="" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-optional">
Encoding
</div>
</td>
<td align="left" valign="top">
<input type="text" name="encoding" size="25" value="utf-8" />
<em>(only used for non-XML and non-HTML content)</em>
</td>
</tr>
<tr>
<td align="left" valign="top">
</td>
<td align="left" valign="top">
<div class="form-element">
<input class="form-element" type="submit" name="submit"
value=" Add " />
<input class="form-element" type="submit" name="submit"
value=" Add and Edit " />
</div>
</td>
</tr>
</table>
</form>
<h1 tal:replace="structure here/manage_page_footer">Footer</h1>
<h1 tal:replace="structure python: context.manage_page_header(management_page_charset='utf-8')">Header</h1>
<h2 tal:define="manage_tabs_message options/manage_tabs_message | nothing"
tal:replace="structure context/manage_tabs">Tabs</h2>
<tal:block define="global body request/other/text | request/form/text
| context/read" />
<form action="" method="post" tal:attributes="action request/URL1">
<input type="hidden" name=":default_method" value="pt_changePrefs">
<input type="hidden" name="encoding" value="utf-8"/>
<table width="100%" cellspacing="0" cellpadding="2" border="0">
<tr>
<td align="left" valign="middle">
<div class="form-optional">
Title
</div>
</td>
<td align="left" valign="middle">
<input type="text" name="title" size="40"
tal:attributes="value request/title | context/title" />
</td>
<td align="left" valign="middle">
<div class="form-label"> Last Modified </div>
</td>
<td align="left" valign="middle">
<div class="form-text"
tal:content="python:context.bobobase_modification_time().strftime('%Y-%m-%d %I:%M %p')">1/1/2000
</div>
</td>
</tr>
<tr>
<td align="left" valign="middle">
<div class="form-label">
Content-Type
</div>
</td>
<td align="left" valign="middle">
<select name="content_type" size="1" tal:define="ct context/content_type">
<option value="text/html" tal:attributes="SELECTED python: ct == 'text/html'">text/html</option>
<option value="text/xml" tal:attributes="SELECTED python: ct == 'text/xml'">text/xml</option>
</select>
</td>
<td align="left" valign="top" colspan=2>
<a href="source.html" tal:condition="context/html">Browse HTML source</a>
<a href="source.xml" tal:condition="not:context/html">Browse XML source</a>
<br>
<input type="hidden" name="expand:int:default" value="0">
<input type="checkbox" value="1" name="expand:int"
tal:attributes="checked request/expand | context/expand">
Expand macros when editing
</td>
</tr>
<!-- XXX: check if 'None' is a proper argument for 'namespace' -->
<tr tal:define="errors python: context.pt_errors(None)" tal:condition="errors">
<tal:block define="global body python:context.document_src({'raw':1})"/>
<td align="left" valign="middle" class="form-label">Errors</td>
<td align="left" valign="middle" style="background-color: #FFDDDD"
colspan="3">
<pre tal:content="python: '\n'.join(errors)">errors</pre>
</td>
</tr>
<tr tal:define="warnings context/pt_warnings" tal:condition="warnings">
<td align="left" valign="middle" class="form-label">Warnings</td>
<td align="left" valign="middle" style="background-color: #FFEEDD"
colspan="3">
<pre tal:content="python: '\n'.join(warnings)">errors</pre>
</td>
</tr>
<tr>
<td align="left" valign="top" colspan="4"
tal:define="width request/dtpref_cols | string:100%;
relative_width python:str(width).endswith('%')">
<textarea name="text:text" wrap="off" style="width: 100%;" rows="20"
tal:condition="relative_width"
tal:attributes="style string:width: $width;;;
rows request/dtpref_rows | default"
tal:content="body">Template Body</textarea>
<textarea name="text:text" wrap="off" rows="20" cols="50"
tal:condition="not:relative_width"
tal:attributes="cols width; rows request/dtpref_rows | default"
tal:content="body">Template Body</textarea>
</td>
</tr>
<tr>
<td align="left" valign="top" colspan="4">
<div class="form-element">
<em tal:condition="context/wl_isLocked">Locked by WebDAV</em>
<input tal:condition="not:context/wl_isLocked"
class="form-element" type="submit"
name="pt_editAction:method" value="Save Changes">
&nbsp;&nbsp;
<input class="form-element" type="submit" name="height" value="Taller">
<input class="form-element" type="submit" name="height" value="Shorter">
<input class="form-element" type="submit" name="width" value="Wider">
<input class="form-element" type="submit" name="width" value="Narrower">
</div>
</td>
</tr>
</table>
</form>
<p class="form-help">
You can upload the text for <span tal:replace="context/title_and_id" />
using the following form.
Choose an existing HTML or XML file from your local computer by clicking
<em>browse</em>. You can also <a href="document_src">click context</a>
to view or download the current text.
</p>
<form action="pt_upload" method="post"
enctype="multipart/form-data">
<table cellpadding="2" cellspacing="0" border="0">
<tr>
<td align="left" valign="top">
<div class="form-label">
File &nbsp;
</div>
</td>
<td align="left" valign="top">
<input type="file" name="file" size="40" value="">
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Encoding &nbsp;
</div>
</td>
<td align="left" valign="top" colspan="2">
<input name="encoding" value="utf-8"/>
</td>
<td align="left" valign="top" colspan="1">
<em>(only used for non-XML and non-XHTML content)</em>
</td>
</tr>
<tr>
<td></td>
<td align="left" valign="top">
<div class="form-element">
<em tal:condition="context/wl_isLocked">Locked by WebDAV</em>
<input tal:condition="not:context/wl_isLocked"
class="form-element" type="submit" value="Upload File">
</div>
</td>
</tr>
</table>
</form>
<h1 tal:replace="structure context/manage_page_footer">Footer</h1>
def all(): # make this directory a package
import testTALES
return testTALES.test_suite()
class harness1:
def __init__(self):
self.__callstack = []
def _assert_(self, name, *args, **kwargs):
self.__callstack.append((name, args, kwargs))
def _complete_(self):
assert len(self.__callstack) == 0, "Harness methods called"
def __getattr__(self, name):
cs = self.__callstack
assert len(cs), 'Unexpected harness method call "%s".' % name
assert cs[0][0] == name, (
'Harness method name "%s" called, "%s" expected.' %
(name, cs[0][0]) )
return self._method_
def _method_(self, *args, **kwargs):
name, aargs, akwargs = self.__callstack.pop(0)
assert aargs == args, "Harness method arguments"
assert akwargs == kwargs, "Harness method keyword args"
class harness2(harness1):
def _assert_(self, name, result, *args, **kwargs):
self.__callstack.append((name, result, args, kwargs))
def _method_(self, *args, **kwargs):
name, result, aargs, akwargs = self.__callstack.pop(0)
assert aargs == args, "Harness method arguments"
assert akwargs == kwargs, "Harness method keyword args"
return result
<p tal:define="now modules/DateTime/DateTime" tal:content="now/isCurrentYear" />
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
<p>[foo](bar/{})</p> <p>[foo](bar/{})</p>
<a href="foo" alt="[default](alttext/{})">link</a> <a href="foo" alt="[default](alttext/{})">link</a>
<p>[dom](${name} was born in ${country}./{'country':'Antarctica','name':'Lomax'})</p> <p>[dom](${name} was born in ${country}./{'country':'Antarctica','name':'Lomax'})</p>
<p>[default](hmm/{'age':'25'})</p> <p>[default](hmm/{'age':u'25'})</p>
</head> </head>
</body> </body>
</html> </html>
...@@ -11,8 +11,10 @@ ...@@ -11,8 +11,10 @@
# #
############################################################################## ##############################################################################
import os, sys, unittest import unittest
import zope.component.testing
from zope.traversing.adapters import DefaultTraversable
from Products.PageTemplates.tests import util from Products.PageTemplates.tests import util
from Products.PageTemplates.PageTemplate import PageTemplate from Products.PageTemplates.PageTemplate import PageTemplate
from Acquisition import Implicit from Acquisition import Implicit
...@@ -43,16 +45,20 @@ class UnitTestSecurityPolicy: ...@@ -43,16 +45,20 @@ class UnitTestSecurityPolicy:
def checkPermission( self, permission, object, context) : def checkPermission( self, permission, object, context) :
return 1 return 1
class DTMLTests(unittest.TestCase): class DTMLTests(zope.component.testing.PlacelessSetup, unittest.TestCase):
def setUp(self): def setUp(self):
self.t=(AqPageTemplate()) super(DTMLTests, self).setUp()
zope.component.provideAdapter(DefaultTraversable, (None,))
self.t = AqPageTemplate()
self.policy = UnitTestSecurityPolicy() self.policy = UnitTestSecurityPolicy()
self.oldPolicy = SecurityManager.setSecurityPolicy( self.policy ) self.oldPolicy = SecurityManager.setSecurityPolicy(self.policy)
noSecurityManager() # Use the new policy. noSecurityManager() # Use the new policy.
def tearDown(self): def tearDown(self):
SecurityManager.setSecurityPolicy( self.oldPolicy ) super(DTMLTests, self).tearDown()
SecurityManager.setSecurityPolicy(self.oldPolicy)
noSecurityManager() # Reset to old policy. noSecurityManager() # Reset to old policy.
def check1(self): def check1(self):
......
import os, sys, unittest import unittest
import zope.component.testing
from zope.traversing.adapters import DefaultTraversable
from Products.PageTemplates import Expressions from Products.PageTemplates import Expressions
from Products.PageTemplates.DeferExpr import LazyWrapper from Products.PageTemplates.DeferExpr import LazyWrapper
...@@ -9,9 +12,12 @@ class Dummy: ...@@ -9,9 +12,12 @@ class Dummy:
def __call__(self): def __call__(self):
return 'dummy' return 'dummy'
class ExpressionTests(unittest.TestCase): class ExpressionTests(zope.component.testing.PlacelessSetup, unittest.TestCase):
def setUp(self): def setUp(self):
super(ExpressionTests, self).setUp()
zope.component.provideAdapter(DefaultTraversable, (None,))
self.e = e = Expressions.getEngine() self.e = e = Expressions.getEngine()
self.ec = e.getContext( self.ec = e.getContext(
one = 1, one = 1,
...@@ -20,9 +26,6 @@ class ExpressionTests(unittest.TestCase): ...@@ -20,9 +26,6 @@ class ExpressionTests(unittest.TestCase):
dummy = Dummy() dummy = Dummy()
) )
def tearDown(self):
del self.e, self.ec
def testCompile(self): def testCompile(self):
'''Test expression compilation''' '''Test expression compilation'''
e = self.e e = self.e
...@@ -50,9 +53,11 @@ class ExpressionTests(unittest.TestCase): ...@@ -50,9 +53,11 @@ class ExpressionTests(unittest.TestCase):
'''Test advanced expression evaluation 1''' '''Test advanced expression evaluation 1'''
ec = self.ec ec = self.ec
assert ec.evaluate('x | nothing') is None assert ec.evaluate('x | nothing') is None
assert ec.evaluate('d/') == 'blank' # empty path elements aren't supported anymore, for the lack
# of a use case
#assert ec.evaluate('d/') == 'blank'
assert ec.evaluate('d/_') == 'under' assert ec.evaluate('d/_') == 'under'
assert ec.evaluate('d/ | nothing') == 'blank' #assert ec.evaluate('d/ | nothing') == 'blank'
assert ec.evaluate('d/?blank') == 'blank' assert ec.evaluate('d/?blank') == 'blank'
def testHybrid(self): def testHybrid(self):
......
...@@ -11,8 +11,10 @@ ...@@ -11,8 +11,10 @@
# #
############################################################################## ##############################################################################
import os, sys, unittest import unittest
import zope.component.testing
from zope.traversing.adapters import DefaultTraversable
from Products.PageTemplates.tests import util from Products.PageTemplates.tests import util
from Products.PageTemplates.PageTemplate import PageTemplate from Products.PageTemplates.PageTemplate import PageTemplate
from Products.PageTemplates.GlobalTranslationService import \ from Products.PageTemplates.GlobalTranslationService import \
...@@ -59,9 +61,12 @@ class UnitTestSecurityPolicy: ...@@ -59,9 +61,12 @@ class UnitTestSecurityPolicy:
def checkPermission( self, permission, object, context) : def checkPermission( self, permission, object, context) :
return 1 return 1
class HTMLTests(unittest.TestCase): class HTMLTests(zope.component.testing.PlacelessSetup, unittest.TestCase):
def setUp(self): def setUp(self):
super(HTMLTests, self).setUp()
zope.component.provideAdapter(DefaultTraversable, (None,))
self.folder = f = Folder() self.folder = f = Folder()
f.laf = AqPageTemplate() f.laf = AqPageTemplate()
f.t = AqPageTemplate() f.t = AqPageTemplate()
...@@ -70,6 +75,7 @@ class HTMLTests(unittest.TestCase): ...@@ -70,6 +75,7 @@ class HTMLTests(unittest.TestCase):
noSecurityManager() # Use the new policy. noSecurityManager() # Use the new policy.
def tearDown(self): def tearDown(self):
super(HTMLTests, self).tearDown()
SecurityManager.setSecurityPolicy( self.oldPolicy ) SecurityManager.setSecurityPolicy( self.oldPolicy )
noSecurityManager() # Reset to old policy. noSecurityManager() # Reset to old policy.
...@@ -156,6 +162,9 @@ class HTMLTests(unittest.TestCase): ...@@ -156,6 +162,9 @@ class HTMLTests(unittest.TestCase):
self.assert_expected(self.folder.t, 'CheckI18nTranslateHooked.html') self.assert_expected(self.folder.t, 'CheckI18nTranslateHooked.html')
setGlobalTranslationService(old_ts) setGlobalTranslationService(old_ts)
def checkImportOldStyleClass(self):
self.assert_expected(self.folder.t, 'CheckImportOldStyleClass.html')
def test_suite(): def test_suite():
return unittest.makeSuite(HTMLTests, 'check') return unittest.makeSuite(HTMLTests, 'check')
......
import os, sys, unittest import unittest
# BBB 2005/05/01 -- to be changed after 12 months
# ignore deprecation warnings on import for now
import warnings
showwarning = warnings.showwarning
warnings.showwarning = lambda *a, **k: None
# this old import should remain here until the TALES.py module is
# completely removed, so that API backward compatibility is properly
# tested
from Products.PageTemplates import TALES from Products.PageTemplates import TALES
from Products.PageTemplates.tests import harness1 # restore warning machinery
import string warnings.showwarning = showwarning
from zope.tales.tests.test_tales import Harness
class DummyUnicodeExpr: class DummyUnicodeExpr:
'''Dummy expression type handler returning unicode''' '''Dummy expression type handler returning unicode'''
...@@ -18,14 +28,14 @@ class TALESTests(unittest.TestCase): ...@@ -18,14 +28,14 @@ class TALESTests(unittest.TestCase):
def testIterator0(self): def testIterator0(self):
'''Test sample Iterator class''' '''Test sample Iterator class'''
context = harness1() context = Harness(self)
it = TALES.Iterator('name', (), context) it = TALES.Iterator('name', (), context)
assert not it.next(), "Empty iterator" assert not it.next(), "Empty iterator"
context._complete_() context._complete_()
def testIterator1(self): def testIterator1(self):
'''Test sample Iterator class''' '''Test sample Iterator class'''
context = harness1() context = Harness(self)
it = TALES.Iterator('name', (1,), context) it = TALES.Iterator('name', (1,), context)
context._assert_('setLocal', 'name', 1) context._assert_('setLocal', 'name', 1)
assert it.next() and not it.next(), "Single-element iterator" assert it.next() and not it.next(), "Single-element iterator"
...@@ -33,7 +43,7 @@ class TALESTests(unittest.TestCase): ...@@ -33,7 +43,7 @@ class TALESTests(unittest.TestCase):
def testIterator2(self): def testIterator2(self):
'''Test sample Iterator class''' '''Test sample Iterator class'''
context = harness1() context = Harness(self)
it = TALES.Iterator('text', 'text', context) it = TALES.Iterator('text', 'text', context)
for c in 'text': for c in 'text':
context._assert_('setLocal', 'text', c) context._assert_('setLocal', 'text', c)
...@@ -104,23 +114,25 @@ class TALESTests(unittest.TestCase): ...@@ -104,23 +114,25 @@ class TALESTests(unittest.TestCase):
def testVariables(self): def testVariables(self):
'''Test variables''' '''Test variables'''
ctxt = self.getContext() ctxt = self.getContext()
c = ctxt.vars
ctxt.beginScope() ctxt.beginScope()
ctxt.setLocal('v1', 1) ctxt.setLocal('v1', 1)
ctxt.setLocal('v2', 2) ctxt.setLocal('v2', 2)
c = ctxt.vars
assert c['v1'] == 1, 'Variable "v1"' assert c['v1'] == 1, 'Variable "v1"'
ctxt.beginScope() ctxt.beginScope()
ctxt.setLocal('v1', 3) ctxt.setLocal('v1', 3)
ctxt.setGlobal('g', 1) ctxt.setGlobal('g', 1)
c = ctxt.vars
assert c['v1'] == 3, 'Inner scope' assert c['v1'] == 3, 'Inner scope'
assert c['v2'] == 2, 'Outer scope' assert c['v2'] == 2, 'Outer scope'
assert c['g'] == 1, 'Global' assert c['g'] == 1, 'Global'
ctxt.endScope() ctxt.endScope()
c = ctxt.vars
assert c['v1'] == 1, "Uncovered local" assert c['v1'] == 1, "Uncovered local"
assert c['g'] == 1, "Global from inner scope" assert c['g'] == 1, "Global from inner scope"
......
...@@ -10,9 +10,9 @@ Note: Tests require Zope >= 2.7 ...@@ -10,9 +10,9 @@ Note: Tests require Zope >= 2.7
import unittest import unittest
import Zope2 import Zope2
import transaction import transaction
import zope.component.testing
from zope.traversing.adapters import DefaultTraversable
from Testing.makerequest import makerequest from Testing.makerequest import makerequest
from Products.PageTemplates.ZopePageTemplate import _default_content_fn
class ZPTRegressions(unittest.TestCase): class ZPTRegressions(unittest.TestCase):
...@@ -35,7 +35,7 @@ class ZPTRegressions(unittest.TestCase): ...@@ -35,7 +35,7 @@ class ZPTRegressions(unittest.TestCase):
def testAddWithoutParams(self): def testAddWithoutParams(self):
pt = self._addPT('pt1') pt = self._addPT('pt1')
default_text = open(_default_content_fn).read() default_text = open(pt._default_content_fn).read()
self.assertEqual(pt.title, '') self.assertEqual(pt.title, '')
self.assertEqual(pt.document_src().strip(), default_text.strip()) self.assertEqual(pt.document_src().strip(), default_text.strip())
...@@ -58,8 +58,12 @@ class ZPTRegressions(unittest.TestCase): ...@@ -58,8 +58,12 @@ class ZPTRegressions(unittest.TestCase):
pt = self.app.pt1 pt = self.app.pt1
self.assertEqual(pt.document_src(), self.text) self.assertEqual(pt.document_src(), self.text)
class ZPTMacros(unittest.TestCase): class ZPTMacros(zope.component.testing.PlacelessSetup, unittest.TestCase):
def setUp(self): def setUp(self):
super(ZPTMacros, self).setUp()
zope.component.provideAdapter(DefaultTraversable, (None,))
transaction.begin() transaction.begin()
self.app = makerequest(Zope2.app()) self.app = makerequest(Zope2.app())
f = self.app.manage_addProduct['PageTemplates'].manage_addPageTemplate f = self.app.manage_addProduct['PageTemplates'].manage_addPageTemplate
...@@ -88,6 +92,12 @@ class ZPTMacros(unittest.TestCase): ...@@ -88,6 +92,12 @@ class ZPTMacros(unittest.TestCase):
</div> </div>
""" """
def tearDown(self):
super(ZPTMacros, self).tearDown()
transaction.abort()
self.app._p_jar.close()
def testMacroExpansion(self): def testMacroExpansion(self):
request = self.app.REQUEST request = self.app.REQUEST
self._addPT('pt1', text=self.text, REQUEST=request) self._addPT('pt1', text=self.text, REQUEST=request)
...@@ -98,8 +108,8 @@ class ZPTMacros(unittest.TestCase): ...@@ -98,8 +108,8 @@ class ZPTMacros(unittest.TestCase):
request = self.app.REQUEST request = self.app.REQUEST
self._addPT('pt1', text=self.text, REQUEST=request) self._addPT('pt1', text=self.text, REQUEST=request)
pt = self.app.pt1 pt = self.app.pt1
pt.pt_render(None, source=1) pt.pt_render(source=True)
self.assertEqual(pt.pt_errors(None), None) self.assertEqual(pt.pt_errors(), None)
class DummyFileUpload: class DummyFileUpload:
......
<h1 tal:replace="structure here/manage_page_header">Header</h1> <h1 tal:replace="structure here/manage_page_header">Header</h1>
<h2 tal:define="form_title string:Add Page Template" <h2 tal:define="form_title string:Add Page Template"
tal:replace="structure here/manage_form_title">Form Title</h2> tal:replace="structure here/manage_form_title">Form Title</h2>
<p class="form-help"> <p class="form-help">Page Templates allow you to use simple HTML or
Page Templates allow you to use simple HTML or XML attributes to XML attributes to create dynamic templates. You may choose to upload
create dynamic templates. You may choose to upload the template text the template text from a local file by typing the file name or using
from a local file by typing the file name or using the <em>browse</em> the <em>browse</em> button.</p>
button.
</p>
<form action="manage_addPageTemplate" method="post" <form action="manage_addPageTemplate" method="post"
enctype="multipart/form-data"> enctype="multipart/form-data">
<table cellspacing="0" cellpadding="2" border="0"> <table cellspacing="0" cellpadding="2" border="0">
<tr> <tr>
<td align="left" valign="top"> <td align="left" valign="top">
<div class="form-label"> <div class="form-label">Id</div>
Id
</div>
</td> </td>
<td align="left" valign="top"> <td align="left" valign="top">
<input type="text" name="id" size="40" /> <input type="text" name="id" size="40" />
...@@ -25,9 +20,7 @@ button. ...@@ -25,9 +20,7 @@ button.
</tr> </tr>
<tr> <tr>
<td align="left" valign="top"> <td align="left" valign="top">
<div class="form-optional"> <div class="form-optional">File</div>
File
</div>
</td> </td>
<td align="left" valign="top"> <td align="left" valign="top">
<input type="file" name="file" size="25" value="" /> <input type="file" name="file" size="25" value="" />
...@@ -35,7 +28,15 @@ button. ...@@ -35,7 +28,15 @@ button.
</tr> </tr>
<tr> <tr>
<td align="left" valign="top"> <td align="left" valign="top">
<div class="form-optional">Encoding</div>
</td> </td>
<td align="left" valign="top">
<input type="text" name="encoding" size="25" value="utf-8" />
<em>(only used for non-XML and non-HTML content)</em>
</td>
</tr>
<tr>
<td align="left" valign="top"></td>
<td align="left" valign="top"> <td align="left" valign="top">
<div class="form-element"> <div class="form-element">
<input class="form-element" type="submit" name="submit" <input class="form-element" type="submit" name="submit"
......
<h1 tal:replace="structure here/manage_page_header">Header</h1> <h1 tal:replace="structure python:context.manage_page_header(management_page_charset='utf-8')">Header</h1>
<h2 tal:define="manage_tabs_message options/manage_tabs_message | nothing" <h2 tal:define="manage_tabs_message options/manage_tabs_message | nothing"
tal:replace="structure here/manage_tabs">Tabs</h2> tal:replace="structure context/manage_tabs">Tabs</h2>
<tal:block define="global body request/other/text | request/form/text <tal:block define="global body request/other/text | request/form/text
| here/read" /> | context/read" />
<form action="" method="post" tal:attributes="action request/URL1"> <form action="" method="post" tal:attributes="action request/URL1">
<input type="hidden" name=":default_method" value="pt_changePrefs"> <input type="hidden" name=":default_method" value="pt_changePrefs" />
<input type="hidden" name="encoding" value="utf-8" />
<table width="100%" cellspacing="0" cellpadding="2" border="0"> <table width="100%" cellspacing="0" cellpadding="2" border="0">
<tr> <tr>
<td align="left" valign="middle"> <td align="left" valign="middle">
<div class="form-optional"> <div class="form-optional">Title</div>
Title
</div>
</td> </td>
<td align="left" valign="middle"> <td align="left" valign="middle">
<input type="text" name="title" size="40" <input type="text" name="title" size="40"
tal:attributes="value request/title | here/title" /> tal:attributes="value request/title | context/title" />
</td> </td>
<td align="left" valign="middle"> <td align="left" valign="middle">
<div class="form-optional"> <div class="form-label">Content-Type</div>
Content-Type
</div>
</td> </td>
<td align="left" valign="middle"> <td align="left" valign="middle">
<input type="text" name="content_type" size="14" <input type="text" name="content_type" size="14"
tal:attributes="value request/content_type | here/content_type" /> tal:attributes="value request/content_type | context/content_type" />
</td> </td>
</tr> </tr>
<tr> <tr>
<td align="left" valign="middle"> <td align="left" valign="middle">
<div class="form-label"> <div class="form-label">Last Modified</div>
Last Modified
</div>
</td> </td>
<td align="left" valign="middle"> <td align="left" valign="middle">
<div class="form-text" <div class="form-text"
tal:content="python:here.bobobase_modification_time().strftime('%Y-%m-%d %I:%M %p')">1/1/2000 tal:content="python:context.bobobase_modification_time().strftime('%Y-%m-%d %I:%M %p')">1/1/2000
</div> </div>
</td> </td>
<td align="left" valign="top" colspan=2> <td align="left" valign="top" colspan="2">
<a href="source.html" tal:condition="here/html">Browse HTML source</a> <a href="source.html" tal:condition="context/html">Browse HTML source</a>
<a href="source.xml" tal:condition="not:here/html">Browse XML source</a> <a href="source.xml" tal:condition="not:context/html">Browse XML source</a>
<br> <br />
<input type="hidden" name="expand:int:default" value="0"> <input type="hidden" name="expand:int:default" value="0" />
<input type="checkbox" value="1" name="expand:int" <input type="checkbox" value="1" name="expand:int"
tal:attributes="checked request/expand | here/expand"> tal:attributes="checked request/expand | context/expand" />
Expand macros when editing Expand macros when editing
</td> </td>
</tr> </tr>
<tr tal:define="errors here/pt_errors" tal:condition="errors"> <tr tal:define="errors context/pt_errors" tal:condition="errors">
<tal:block define="global body python:here.document_src({'raw':1})"/> <tal:block define="global body python:context.document_src({'raw':1})" />
<td align="left" valign="middle" class="form-label">Errors</td> <td align="left" valign="middle" class="form-label">Errors</td>
<td align="left" valign="middle" style="background-color: #FFDDDD" <td align="left" valign="middle" style="background-color: #FFDDDD"
colspan="3"> colspan="3">
<pre tal:content="python:modules['string'].join(errors, '\n')">errors</pre> <pre tal:content="python:'\n'.join(errors)">errors</pre>
</td> </td>
</tr> </tr>
<tr tal:define="warnings here/pt_warnings" tal:condition="warnings"> <tr tal:define="warnings context/pt_warnings" tal:condition="warnings">
<td align="left" valign="middle" class="form-label">Warnings</td> <td align="left" valign="middle" class="form-label">Warnings</td>
<td align="left" valign="middle" style="background-color: #FFEEDD" <td align="left" valign="middle" style="background-color: #FFEEDD"
colspan="3"> colspan="3">
<pre tal:content="python:modules['string'].join(warnings, '\n')">errors</pre> <pre tal:content="python:'\n'.join(warnings)">errors</pre>
</td> </td>
</tr> </tr>
...@@ -85,64 +80,59 @@ ...@@ -85,64 +80,59 @@
<tr> <tr>
<td align="left" valign="top" colspan="4"> <td align="left" valign="top" colspan="4">
<div class="form-element"> <div class="form-element">
<em tal:condition="here/wl_isLocked">Locked by WebDAV</em> <em tal:condition="context/wl_isLocked">Locked by WebDAV</em>
<input tal:condition="not:here/wl_isLocked" <input tal:condition="not:context/wl_isLocked"
class="form-element" type="submit" class="form-element" type="submit"
name="pt_editAction:method" value="Save Changes"> name="pt_editAction:method" value="Save Changes">
&nbsp;&nbsp; &nbsp;&nbsp;
<input class="form-element" type="submit" name="height" value="Taller"> <input class="form-element" type="submit" name="height" value="Taller" />
<input class="form-element" type="submit" name="height" value="Shorter"> <input class="form-element" type="submit" name="height" value="Shorter" />
<input class="form-element" type="submit" name="width" value="Wider"> <input class="form-element" type="submit" name="width" value="Wider" />
<input class="form-element" type="submit" name="width" value="Narrower"> <input class="form-element" type="submit" name="width" value="Narrower" />
</div> </div>
</td> </td>
</tr> </tr>
</table> </table>
</form> </form>
<p class="form-help"> <p class="form-help">You can upload the text for
You can upload the text for <span tal:replace="here/title_and_id" /> <span tal:replace="context/title_and_id" /> using the following form.
using the following form. Choose an existing HTML or XML file from your local computer by
Choose an existing HTML or XML file from your local computer by clicking clicking <em>browse</em>. You can also <a href="document_src">click
<em>browse</em>. You can also <a href="document_src">click here</a> context</a> to view or download the current text.</p>
to view or download the current text.
</p>
<form action="pt_upload" method="post" <form action="pt_upload" method="post" enctype="multipart/form-data">
enctype="multipart/form-data">
<table cellpadding="2" cellspacing="0" border="0"> <table cellpadding="2" cellspacing="0" border="0">
<tr> <tr>
<td align="left" valign="top"> <td align="left" valign="top">
<div class="form-label"> <div class="form-label">File &nbsp;</div>
File &nbsp;
</div>
</td> </td>
<td align="left" valign="top"> <td align="left" valign="top">
<input type="file" name="file" size="25" value=""> <input type="file" name="file" size="40" value="" />
</td> </td>
</tr> </tr>
<tr tal:condition="context/management_page_charset|nothing"> <tr>
<td align="left" valign="top"> <td align="left" valign="top">
<div class="form-label"> <div class="form-label">Encoding &nbsp;</div>
Encoding &nbsp;
</div>
</td> </td>
<td align="left" valign="top"> <td align="left" valign="top" colspan="2">
<input name="charset" value="" <input name="encoding" value="utf-8" />
tal:attributes="value here/management_page_charset|default" /> </td>
<td align="left" valign="top" colspan="1">
<em>(only used for non-XML and non-XHTML content)</em>
</td> </td>
</tr> </tr>
<tr> <tr>
<td></td> <td></td>
<td align="left" valign="top"> <td align="left" valign="top">
<div class="form-element"> <div class="form-element">
<em tal:condition="here/wl_isLocked">Locked by WebDAV</em> <em tal:condition="context/wl_isLocked">Locked by WebDAV</em>
<input tal:condition="not:here/wl_isLocked" <input tal:condition="not:context/wl_isLocked"
class="form-element" type="submit" value="Upload File"> class="form-element" type="submit" value="Upload File" />
</div> </div>
</td> </td>
</tr> </tr>
</table> </table>
</form> </form>
<h1 tal:replace="structure here/manage_page_footer">Footer</h1> <h1 tal:replace="structure context/manage_page_footer">Footer</h1>
...@@ -13,238 +13,13 @@ ...@@ -13,238 +13,13 @@
############################################################################## ##############################################################################
""" """
Dummy TALES engine so that I can test out the TAL implementation. Dummy TALES engine so that I can test out the TAL implementation.
"""
import re
import sys
from TALDefs import NAME_RE, TALESError, ErrorInfo
from ITALES import ITALESCompiler, ITALESEngine
from DocumentTemplate.DT_Util import ustr
class _Default:
pass
Default = _Default()
name_match = re.compile(r"(?s)(%s):(.*)\Z" % NAME_RE).match
class CompilerError(Exception):
pass
class DummyEngine:
position = None
source_file = None
__implements__ = ITALESCompiler, ITALESEngine
def __init__(self, macros=None):
if macros is None:
macros = {}
self.macros = macros
dict = {'nothing': None, 'default': Default}
self.locals = self.globals = dict
self.stack = [dict]
self.translationService = DummyTranslationService()
def getCompilerError(self):
return CompilerError
def getCompiler(self):
return self
def setSourceFile(self, source_file):
self.source_file = source_file
def setPosition(self, position):
self.position = position
def compile(self, expr):
return "$%s$" % expr
def uncompile(self, expression):
assert (expression.startswith("$") and expression.endswith("$"),
expression)
return expression[1:-1]
def beginScope(self):
self.stack.append(self.locals)
def endScope(self):
assert len(self.stack) > 1, "more endScope() than beginScope() calls"
self.locals = self.stack.pop()
def setLocal(self, name, value):
if self.locals is self.stack[-1]:
# Unmerge this scope's locals from previous scope of first set
self.locals = self.locals.copy()
self.locals[name] = value
def setGlobal(self, name, value):
self.globals[name] = value
def evaluate(self, expression):
assert (expression.startswith("$") and expression.endswith("$"),
expression)
expression = expression[1:-1]
m = name_match(expression)
if m:
type, expr = m.group(1, 2)
else:
type = "path"
expr = expression
if type in ("string", "str"):
return expr
if type in ("path", "var", "global", "local"):
return self.evaluatePathOrVar(expr)
if type == "not":
return not self.evaluate(expr)
if type == "exists":
return self.locals.has_key(expr) or self.globals.has_key(expr)
if type == "python":
try:
return eval(expr, self.globals, self.locals)
except:
raise TALESError("evaluation error in %s" % `expr`)
if type == "position":
# Insert the current source file name, line number,
# and column offset.
if self.position:
lineno, offset = self.position
else:
lineno, offset = None, None
return '%s (%s,%s)' % (self.source_file, lineno, offset)
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): BBB 2005/05/01 -- to be removed after 12 months
return self.evaluate(expr) """
import zope.deprecation
def evaluateBoolean(self, expr): zope.deprecation.moved('zope.tal.dummyengine', '2.12')
return self.evaluate(expr)
def evaluateText(self, expr):
text = self.evaluate(expr)
if text is not None and text is not Default:
text = ustr(text)
return text
def evaluateStructure(self, expr):
# XXX Should return None or a DOM tree
return self.evaluate(expr)
def evaluateSequence(self, expr):
# XXX Should return a sequence
return self.evaluate(expr)
def evaluateMacro(self, macroName):
assert (macroName.startswith("$") and macroName.endswith("$"),
macroName)
macroName = macroName[1:-1]
file, localName = self.findMacroFile(macroName)
if not file:
# Local macro
macro = self.macros[localName]
else:
# External macro
import driver
program, macros = driver.compilefile(file)
macro = macros.get(localName)
if not macro:
raise TALESError("macro %s not found in file %s" %
(localName, file))
return macro
def findMacroDocument(self, macroName):
file, localName = self.findMacroFile(macroName)
if not file:
return file, localName
import driver
doc = driver.parsefile(file)
return doc, localName
def findMacroFile(self, macroName):
if not macroName:
raise TALESError("empty macro name")
i = macroName.rfind('/')
if i < 0:
# No slash -- must be a locally defined macro
return None, macroName
else:
# Up to last slash is the filename
fileName = macroName[:i]
localName = macroName[i+1:]
return fileName, localName
def setRepeat(self, name, expr):
seq = self.evaluateSequence(expr)
return Iterator(name, seq, self)
def createErrorInfo(self, err, position):
return ErrorInfo(err, position)
def getDefault(self):
return Default
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
self.engine = engine
self.nextIndex = 0
def next(self):
i = self.nextIndex
try:
item = self.seq[i]
except IndexError:
return 0
self.nextIndex = i+1
self.engine.setLocal(self.name, item)
return 1
class DummyDomain:
def translate(self, msgid, mapping=None, context=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
text = msgid
if msgid == "don't translate me":
if default is not None:
text = default
else:
text = msgid.upper()
def repl(m, mapping=mapping): from zope.tal.dummyengine import DummyTranslationDomain as DummyDomain
return ustr(mapping[m.group(m.lastindex).lower()])
cre = re.compile(r'\$(?:(%s)|\{(%s)\})' % (NAME_RE, NAME_RE))
return cre.sub(repl, text)
class DummyTranslationService: class DummyTranslationService:
......
This diff is collapsed.
"""Interface that a TALES engine provides to the METAL/TAL implementation.""" """Interface that a TALES engine provides to the METAL/TAL implementation."""
try: import zope.deferredimport
from Interface import Interface zope.deferredimport.deprecatedFrom(
from Interface.Attribute import Attribute "The TAL implementation has moved to zope.tal. Import expression "
except: "interfaces from zope.tal.interfaces. The old references will be "
# Before 2.7 "gone in Zope 2.12.",
class Interface: pass 'zope.tal.interfaces'
def Attribute(*args): pass 'ITALExpressionCompiler', 'ITALExpressionEngine', 'ITALExpressionErrorInfo'
)
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 getCompiler():
"""Return an object that supports ITALESCompiler."""
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, default=None):
"""
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.")
...@@ -13,179 +13,16 @@ ...@@ -13,179 +13,16 @@
############################################################################## ##############################################################################
""" """
Common definitions used by TAL and METAL compilation an transformation. Common definitions used by TAL and METAL compilation an transformation.
"""
from types import ListType, TupleType
from ITALES import ITALESErrorInfo
TAL_VERSION = "1.5"
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
ZOPE_TAL_NS = "http://xml.zope.org/namespaces/tal"
ZOPE_METAL_NS = "http://xml.zope.org/namespaces/metal"
ZOPE_I18N_NS = "http://xml.zope.org/namespaces/i18n"
# 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",
"use-macro",
"define-slot",
"fill-slot",
"slot",
]
KNOWN_TAL_ATTRIBUTES = [
"define",
"condition",
"content",
"replace",
"repeat",
"attributes",
"on-error",
"omit-tag",
"tal tag",
]
KNOWN_I18N_ATTRIBUTES = [
"translate",
"domain",
"target",
"source",
"attributes",
"data",
"name",
]
class TALError(Exception):
def __init__(self, msg, position=(None, None)):
assert msg != ""
self.msg = msg
self.lineno = position[0]
self.offset = position[1]
self.filename = None
def setFile(self, filename):
self.filename = filename
def __str__(self):
result = self.msg
if self.lineno is not None:
result = result + ", at line %d" % self.lineno
if self.offset is not None:
result = result + ", column %d" % (self.offset + 1)
if self.filename is not None:
result = result + ', in file %s' % self.filename
return result
class METALError(TALError):
pass
class TALESError(TALError):
pass
class I18NError(TALError): BBB 2005/05/01 -- to be removed after 12 months
pass """
import zope.deprecation
zope.deprecation.moved('zope.tal.taldefs', '2.12')
class ErrorInfo:
import zope.deferredimport
__implements__ = ITALESErrorInfo zope.deferredimport.deprecated(
"TALESError has been renamed TALExpressionError and should be "
def __init__(self, err, position=(None, None)): "imported from zope.tal.taldefs. This reference will be gone in "
if isinstance(err, Exception): "Zope 2.12.",
self.type = err.__class__ TALESError = 'zope.tal.taldefs.TALExpressionError'
self.value = err )
else:
self.type = err
self.value = None
self.lineno = position[0]
self.offset = position[1]
import re
_attr_re = re.compile(r"\s*([^\s]+)\s+([^\s].*)\Z", re.S)
_subst_re = re.compile(r"\s*(?:(text|structure)\s+)?(.*)\Z", re.S)
del re
def parseAttributeReplacements(arg, xml):
dict = {}
for part in splitParts(arg):
m = _attr_re.match(part)
if not m:
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`)
dict[name] = expr
return dict
def parseSubstitution(arg, position=(None, None)):
m = _subst_re.match(arg)
if not m:
raise TALError("Bad syntax in substitution text: " + `arg`, position)
key, expr = m.group(1, 2)
if not key:
key = "text"
return key, expr
def splitParts(arg):
# Break in pieces at undoubled semicolons and
# change double semicolons to singles:
arg = arg.replace(";;", "\0")
parts = arg.split(';')
parts = [p.replace("\0", ";") for p in parts]
if len(parts) > 1 and not parts[-1].strip():
del parts[-1] # It ended in a semicolon
return parts
def isCurrentVersion(program):
version = getProgramVersion(program)
return version == TAL_VERSION
def getProgramMode(program):
version = getProgramVersion(program)
if (version == TAL_VERSION and isinstance(program[1], TupleType) and
len(program[1]) == 2):
opcode, mode = program[1]
if opcode == "mode":
return mode
return None
def getProgramVersion(program):
if (len(program) >= 2 and
isinstance(program[0], TupleType) and len(program[0]) == 2):
opcode, version = program[0]
if opcode == "version":
return version
return None
import re
_ent1_re = re.compile('&(?![A-Z#])', re.I)
_entch_re = re.compile('&([A-Z][A-Z0-9]*)(?![A-Z0-9;])', re.I)
_entn1_re = re.compile('&#(?![0-9X])', re.I)
_entnx_re = re.compile('&(#X[A-F0-9]*)(?![A-F0-9;])', re.I)
_entnd_re = re.compile('&(#[0-9][0-9]*)(?![0-9;])')
del re
def attrEscape(s):
"""Replace special characters '&<>' by character entities,
except when '&' already begins a syntactically valid entity."""
s = _ent1_re.sub('&amp;', s)
s = _entch_re.sub(r'&amp;\1', s)
s = _entn1_re.sub('&amp;#', s)
s = _entnx_re.sub(r'&amp;\1', s)
s = _entnd_re.sub(r'&amp;\1', s)
s = s.replace('<', '&lt;')
s = s.replace('>', '&gt;')
s = s.replace('"', '&quot;')
return s
This diff is collapsed.
This diff is collapsed.
...@@ -13,131 +13,8 @@ ...@@ -13,131 +13,8 @@
############################################################################## ##############################################################################
""" """
Parse XML and compile to TALInterpreter intermediate code. Parse XML and compile to TALInterpreter intermediate code.
"""
from XMLParser import XMLParser
from TALDefs import XML_NS, ZOPE_I18N_NS, ZOPE_METAL_NS, ZOPE_TAL_NS
from TALGenerator import TALGenerator
class TALParser(XMLParser):
ordered_attributes = 1
def __init__(self, gen=None): # Override
XMLParser.__init__(self)
if gen is None:
gen = TALGenerator()
self.gen = gen
self.nsStack = []
self.nsDict = {XML_NS: 'xml'}
self.nsNew = []
def getCode(self):
return self.gen.getCode()
def getWarnings(self):
return ()
def StartNamespaceDeclHandler(self, prefix, uri):
self.nsStack.append(self.nsDict.copy())
self.nsDict[uri] = prefix
self.nsNew.append((prefix, uri))
def EndNamespaceDeclHandler(self, prefix):
self.nsDict = self.nsStack.pop()
def StartElementHandler(self, name, attrs): BBB 2005/05/01 -- to be removed after 12 months
if self.ordered_attributes: """
# attrs is a list of alternating names and values import zope.deprecation
attrlist = [] zope.deprecation.moved('zope.tal.talparser', '2.12')
for i in range(0, len(attrs), 2):
key = attrs[i]
value = attrs[i+1]
attrlist.append((key, value))
else:
# attrs is a dict of {name: value}
attrlist = attrs.items()
attrlist.sort() # For definiteness
name, attrlist, taldict, metaldict, i18ndict \
= self.process_ns(name, attrlist)
attrlist = self.xmlnsattrs() + attrlist
self.gen.emitStartElement(name, attrlist, taldict, metaldict, i18ndict)
def process_ns(self, name, attrlist):
taldict = {}
metaldict = {}
i18ndict = {}
fixedattrlist = []
name, namebase, namens = self.fixname(name)
for key, value in attrlist:
key, keybase, keyns = self.fixname(key)
ns = keyns or namens # default to tag namespace
item = key, value
if ns == 'metal':
metaldict[keybase] = value
item = item + ("metal",)
elif ns == 'tal':
taldict[keybase] = value
item = item + ("tal",)
elif ns == 'i18n':
i18ndict[keybase] = value
item = item + ('i18n',)
fixedattrlist.append(item)
if namens in ('metal', 'tal', 'i18n'):
taldict['tal tag'] = namens
return name, fixedattrlist, taldict, metaldict, i18ndict
def xmlnsattrs(self):
newlist = []
for prefix, uri in self.nsNew:
if prefix:
key = "xmlns:" + prefix
else:
key = "xmlns"
if uri in (ZOPE_METAL_NS, ZOPE_TAL_NS, ZOPE_I18N_NS):
item = (key, uri, "xmlns")
else:
item = (key, uri)
newlist.append(item)
self.nsNew = []
return newlist
def fixname(self, name):
if ' ' in name:
uri, name = name.split(' ')
prefix = self.nsDict[uri]
prefixed = name
if prefix:
prefixed = "%s:%s" % (prefix, name)
ns = 'x'
if uri == ZOPE_TAL_NS:
ns = 'tal'
elif uri == ZOPE_METAL_NS:
ns = 'metal'
elif uri == ZOPE_I18N_NS:
ns = 'i18n'
return (prefixed, name, ns)
return (name, name, None)
def EndElementHandler(self, name):
name = self.fixname(name)[0]
self.gen.emitEndElement(name)
def DefaultHandler(self, text):
self.gen.emitRawText(text)
def test():
import sys
p = TALParser()
file = "tests/input/test01.xml"
if sys.argv[1:]:
file = sys.argv[1]
p.parseFile(file)
program, macros = p.getCode()
from TALInterpreter import TALInterpreter
from DummyEngine import DummyEngine
engine = DummyEngine(macros)
TALInterpreter(program, macros, engine, sys.stdout, wrap=0)()
if __name__ == "__main__":
test()
...@@ -13,29 +13,9 @@ ...@@ -13,29 +13,9 @@
############################################################################## ##############################################################################
"""Translation context object for the TALInterpreter's I18N support. """Translation context object for the TALInterpreter's I18N support.
The translation context provides a container for the information BBB 2005/05/01 -- to be removed after 12 months
needed to perform translation of a marked string from a page template.
$Id$ $Id$
""" """
import zope.deprecation
DEFAULT_DOMAIN = "default" zope.deprecation.moved('zope.tal.translationcontext', '2.12')
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
...@@ -13,75 +13,11 @@ ...@@ -13,75 +13,11 @@
############################################################################## ##############################################################################
""" """
Generic expat-based XML parser base class. Generic expat-based XML parser base class.
BBB 2005/05/01 -- to be removed after 12 months
""" """
import zope.deprecation
zope.deprecation.moved('zope.tal.xmlparser', '2.12')
import xml.parsers.expat import xml.parsers.expat
from logging import getLogger
LOG = getLogger('TAL')
XMLParseError = xml.parsers.expat.ExpatError XMLParseError = xml.parsers.expat.ExpatError
class XMLParser:
ordered_attributes = 0
handler_names = [
"StartElementHandler",
"EndElementHandler",
"ProcessingInstructionHandler",
"CharacterDataHandler",
"UnparsedEntityDeclHandler",
"NotationDeclHandler",
"StartNamespaceDeclHandler",
"EndNamespaceDeclHandler",
"CommentHandler",
"StartCdataSectionHandler",
"EndCdataSectionHandler",
"DefaultHandler",
"DefaultHandlerExpand",
"NotStandaloneHandler",
"ExternalEntityRefHandler",
"XmlDeclHandler",
"StartDoctypeDeclHandler",
"EndDoctypeDeclHandler",
"ElementDeclHandler",
"AttlistDeclHandler"
]
def __init__(self, encoding=None):
self.parser = p = self.createParser()
if self.ordered_attributes:
try:
self.parser.ordered_attributes = self.ordered_attributes
except AttributeError:
LOG.info("Can't set ordered_attributes")
self.ordered_attributes = 0
for name in self.handler_names:
method = getattr(self, name, None)
if method is not None:
try:
setattr(p, name, method)
except AttributeError:
LOG.error("Can't set expat handler %s" % name)
def createParser(self, encoding=None):
return xml.parsers.expat.ParserCreate(encoding, ' ')
def parseFile(self, filename):
self.parseStream(open(filename))
def parseString(self, s):
self.parser.Parse(s, 1)
def parseURL(self, url):
import urllib
self.parseStream(urllib.urlopen(url))
def parseStream(self, stream):
self.parser.ParseFile(stream)
def parseFragment(self, s, end=0):
self.parser.Parse(s, end)
...@@ -34,165 +34,11 @@ Options: ...@@ -34,165 +34,11 @@ Options:
Leave TAL/METAL attributes in output Leave TAL/METAL attributes in output
-i -i
Leave I18N substitution strings un-interpolated. Leave I18N substitution strings un-interpolated.
"""
import os
import sys
import getopt
if __name__ == "__main__":
import setpath # Local hack to tweak sys.path etc.
# Import local classes
import TALDefs
from DummyEngine import DummyEngine
from DummyEngine import DummyTranslationService
FILE = "tests/input/test01.xml"
class TestTranslations(DummyTranslationService):
def translate(self, domain, msgid, mapping=None, context=None,
target_language=None, default=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,
default=default)
class TestEngine(DummyEngine):
def __init__(self, macros=None):
DummyEngine.__init__(self, macros)
self.translationService = TestTranslations()
def evaluatePathOrVar(self, expr): BBB 2005/05/01 -- to be removed after 12 months
if expr == 'here/currentTime': """
return {'hours' : 6, import zope.deprecation
'minutes': 59, zope.deprecation.moved('zope.tal.driver', '2.12')
'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():
macros = 0
mode = None
showcode = 0
showtal = -1
strictinsert = 1
i18nInterpolate = 1
try:
opts, args = getopt.getopt(sys.argv[1:], "hHxlmsti",
['help', 'html', 'xml'])
except getopt.error, msg:
usage(2, msg)
for opt, arg in opts:
if opt in ('-h', '--help'):
usage(0)
if opt in ('-H', '--html'):
if mode == 'xml':
usage(1, '--html and --xml are mutually exclusive')
mode = "html"
if opt == '-l':
strictinsert = 0
if opt == '-m':
macros = 1
if opt == '-n':
versionTest = 0
if opt in ('-x', '--xml'):
if mode == 'html':
usage(1, '--html and --xml are mutually exclusive')
mode = "xml"
if opt == '-s':
showcode = 1
if opt == '-t':
showtal = 1
if opt == '-i':
i18nInterpolate = 0
if args:
file = args[0]
else:
file = FILE
it = compilefile(file, mode)
if showcode:
showit(it)
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,
strictinsert=1, i18nInterpolate=1):
from TALInterpreter import TALInterpreter
program, macros = it
assert TALDefs.isCurrentVersion(program)
if engine is None:
engine = DummyEngine(macros)
TALInterpreter(program, macros, engine, stream, wrap=0,
tal=tal, showtal=showtal, strictinsert=strictinsert,
i18nInterpolate=i18nInterpolate)()
def compilefile(file, mode=None):
assert mode in ("html", "xml", None)
if mode is None:
ext = os.path.splitext(file)[1]
if ext.lower() in (".html", ".htm"):
mode = "html"
else:
mode = "xml"
if mode == "html":
from HTMLTALParser import HTMLTALParser
p = HTMLTALParser()
else:
from TALParser import TALParser
p = TALParser()
p.parseFile(file)
return p.getCode()
def showit(it):
from pprint import pprint
pprint(it)
if __name__ == "__main__": if __name__ == "__main__":
main() main()
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
<div i18n:translate="">At the tone the time will be <div i18n:translate="">At the tone the time will be
<span i18n:data="here/currentTime" <span i18n:data="here/currentTime"
i18n:translate="timefmt" i18n:translate="timefmt"
i18n:name="time">2:32 pm</span>... beep!</div> i18n:name="time"
tal:omit-tag="">2:32 pm</span>... beep!</div>
This diff is collapsed.
<div>AT THE TONE THE TIME WILL BE 59 MINUTES AFTER 6 PM... BEEP!</div> <div>AT THE TONE THE TIME WILL BE 59 minutes after 6 PM... BEEP!</div>
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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