summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: 39d5aeb)
raw | patch | inline | side by side (parent: 39d5aeb)
author | richard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2> | |
Fri, 30 Aug 2002 08:27:34 +0000 (08:27 +0000) | ||
committer | richard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2> | |
Fri, 30 Aug 2002 08:27:34 +0000 (08:27 +0000) |
git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@1001 57a73879-2fb5-44c3-a270-3262357dd7e2
PageTemplates/Expressions.py | [new file with mode: 0644] | patch | blob |
PageTemplates/PageTemplate.py | [new file with mode: 0755] | patch | blob |
PageTemplates/PathIterator.py | [new file with mode: 0644] | patch | blob |
PageTemplates/PythonExpr.py | [new file with mode: 0644] | patch | blob |
PageTemplates/README.txt | [new file with mode: 0644] | patch | blob |
PageTemplates/TALES.py | [new file with mode: 0644] | patch | blob |
PageTemplates/__init__.py | [new file with mode: 0644] | patch | blob |
diff --git a/PageTemplates/Expressions.py b/PageTemplates/Expressions.py
--- /dev/null
@@ -0,0 +1,375 @@
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+#
+##############################################################################
+
+"""Page Template Expression Engine
+
+Page Template-specific implementation of TALES, with handlers
+for Python expressions, string literals, and paths.
+"""
+
+__version__='$Revision: 1.1 $'[11:-2]
+
+import re, sys
+from TALES import Engine, CompilerError, _valid_name, NAME_RE, \
+ Undefined, Default, _parse_expr
+from string import strip, split, join, replace, lstrip
+from Acquisition import aq_base, aq_inner, aq_parent
+
+
+_engine = None
+def getEngine():
+ global _engine
+ if _engine is None:
+ from PathIterator import Iterator
+ _engine = Engine(Iterator)
+ installHandlers(_engine)
+ return _engine
+
+def installHandlers(engine):
+ reg = engine.registerType
+ pe = PathExpr
+ for pt in ('standard', 'path', 'exists', 'nocall'):
+ reg(pt, pe)
+ reg('string', StringExpr)
+ reg('python', PythonExpr)
+ reg('not', NotExpr)
+ reg('defer', DeferExpr)
+
+if sys.modules.has_key('Zope'):
+ import AccessControl
+ from AccessControl import getSecurityManager
+ try:
+ from AccessControl import Unauthorized
+ except ImportError:
+ Unauthorized = "Unauthorized"
+ if hasattr(AccessControl, 'full_read_guard'):
+ from ZRPythonExpr import PythonExpr, _SecureModuleImporter, \
+ call_with_ns
+ else:
+ from ZPythonExpr import PythonExpr, _SecureModuleImporter, \
+ call_with_ns
+else:
+ from PythonExpr import getSecurityManager, PythonExpr
+ try:
+ from zExceptions import Unauthorized
+ except ImportError:
+ Unauthorized = "Unauthorized"
+ def call_with_ns(f, ns, arg=1):
+ if arg==2:
+ return f(None, ns)
+ else:
+ return f(ns)
+
+ class _SecureModuleImporter:
+ """Simple version of the importer for use with trusted code."""
+ __allow_access_to_unprotected_subobjects__ = 1
+ def __getitem__(self, module):
+ __import__(module)
+ return sys.modules[module]
+
+SecureModuleImporter = _SecureModuleImporter()
+
+Undefs = (Undefined, AttributeError, KeyError,
+ TypeError, IndexError, Unauthorized)
+
+def render(ob, ns):
+ """
+ Calls the object, possibly a document template, or just returns it if
+ not callable. (From DT_Util.py)
+ """
+ if hasattr(ob, '__render_with_namespace__'):
+ ob = call_with_ns(ob.__render_with_namespace__, ns)
+ else:
+ base = aq_base(ob)
+ if callable(base):
+ try:
+ if getattr(base, 'isDocTemp', 0):
+ ob = call_with_ns(ob, ns, 2)
+ else:
+ ob = ob()
+ except AttributeError, n:
+ if str(n) != '__call__':
+ raise
+ return ob
+
+class SubPathExpr:
+ def __init__(self, path):
+ self._path = path = split(strip(path), '/')
+ self._base = base = path.pop(0)
+ if not _valid_name(base):
+ raise CompilerError, 'Invalid variable name "%s"' % base
+ # Parse path
+ self._dp = dp = []
+ for i in range(len(path)):
+ e = path[i]
+ if e[:1] == '?' and _valid_name(e[1:]):
+ dp.append((i, e[1:]))
+ dp.reverse()
+
+ def _eval(self, econtext,
+ list=list, isinstance=isinstance, StringType=type('')):
+ vars = econtext.vars
+ path = self._path
+ if self._dp:
+ path = list(path) # Copy!
+ for i, varname in self._dp:
+ val = vars[varname]
+ if isinstance(val, StringType):
+ path[i] = val
+ else:
+ # If the value isn't a string, assume it's a sequence
+ # of path names.
+ path[i:i+1] = list(val)
+ __traceback_info__ = base = self._base
+ if base == 'CONTEXTS':
+ ob = econtext.contexts
+ else:
+ ob = vars[base]
+ if isinstance(ob, DeferWrapper):
+ ob = ob()
+ if path:
+ ob = restrictedTraverse(ob, path, getSecurityManager())
+ return ob
+
+class PathExpr:
+ def __init__(self, name, expr, engine):
+ self._s = expr
+ self._name = name
+ paths = split(expr, '|')
+ self._subexprs = []
+ add = self._subexprs.append
+ for i in range(len(paths)):
+ path = lstrip(paths[i])
+ if _parse_expr(path):
+ # This part is the start of another expression type,
+ # so glue it back together and compile it.
+ add(engine.compile(lstrip(join(paths[i:], '|'))))
+ break
+ add(SubPathExpr(path)._eval)
+
+ def _exists(self, econtext):
+ for expr in self._subexprs:
+ try:
+ expr(econtext)
+ except Undefs:
+ pass
+ else:
+ return 1
+ return 0
+
+ def _eval(self, econtext,
+ isinstance=isinstance, StringType=type(''), render=render):
+ for expr in self._subexprs[:-1]:
+ # Try all but the last subexpression, skipping undefined ones.
+ try:
+ ob = expr(econtext)
+ except Undefs:
+ pass
+ else:
+ break
+ else:
+ # On the last subexpression allow exceptions through.
+ ob = self._subexprs[-1](econtext)
+
+ if self._name == 'nocall' or isinstance(ob, StringType):
+ return ob
+ # Return the rendered object
+ return render(ob, econtext.vars)
+
+ def __call__(self, econtext):
+ if self._name == 'exists':
+ return self._exists(econtext)
+ return self._eval(econtext)
+
+ def __str__(self):
+ return '%s expression %s' % (self._name, `self._s`)
+
+ def __repr__(self):
+ return '%s:%s' % (self._name, `self._s`)
+
+
+_interp = re.compile(r'\$(%(n)s)|\${(%(n)s(?:/%(n)s)*)}' % {'n': NAME_RE})
+
+class StringExpr:
+ def __init__(self, name, expr, engine):
+ self._s = expr
+ if '%' in expr:
+ expr = replace(expr, '%', '%%')
+ self._vars = vars = []
+ if '$' in expr:
+ parts = []
+ for exp in split(expr, '$$'):
+ if parts: parts.append('$')
+ m = _interp.search(exp)
+ while m is not None:
+ parts.append(exp[:m.start()])
+ parts.append('%s')
+ vars.append(PathExpr('path', m.group(1) or m.group(2),
+ engine))
+ exp = exp[m.end():]
+ m = _interp.search(exp)
+ if '$' in exp:
+ raise CompilerError, (
+ '$ must be doubled or followed by a simple path')
+ parts.append(exp)
+ expr = join(parts, '')
+ self._expr = expr
+
+ def __call__(self, econtext):
+ vvals = []
+ for var in self._vars:
+ v = var(econtext)
+ if isinstance(v, Exception):
+ raise v
+ vvals.append(v)
+ return self._expr % tuple(vvals)
+
+ def __str__(self):
+ return 'string expression %s' % `self._s`
+
+ def __repr__(self):
+ return 'string:%s' % `self._s`
+
+class NotExpr:
+ def __init__(self, name, expr, compiler):
+ self._s = expr = lstrip(expr)
+ self._c = compiler.compile(expr)
+
+ def __call__(self, econtext):
+ return not econtext.evaluateBoolean(self._c)
+
+ def __repr__(self):
+ return 'not:%s' % `self._s`
+
+class DeferWrapper:
+ 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:
+ def __init__(self, name, expr, compiler):
+ self._s = expr = lstrip(expr)
+ self._c = compiler.compile(expr)
+
+ def __call__(self, econtext):
+ return DeferWrapper(self._c, econtext)
+
+ def __repr__(self):
+ return 'defer:%s' % `self._s`
+
+
+def restrictedTraverse(self, path, securityManager,
+ get=getattr, has=hasattr, N=None, M=[],
+ TupleType=type(()) ):
+
+ REQUEST = {'path': path}
+ REQUEST['TraversalRequestNameStack'] = path = path[:] # Copy!
+ if not path[0]:
+ # If the path starts with an empty string, go to the root first.
+ self = self.getPhysicalRoot()
+ if not securityManager.validateValue(self):
+ raise Unauthorized, name
+ path.pop(0)
+
+ path.reverse()
+ validate = securityManager.validate
+ object = self
+ #print 'TRAVERSE', (object, path)
+ while path:
+ __traceback_info__ = REQUEST
+ name = path.pop()
+
+ if isinstance(name, TupleType):
+ object = apply(object, name)
+ continue
+
+ if name[0] == '_':
+ # Never allowed in a URL.
+ raise AttributeError, name
+
+ if name=='..':
+ o = get(object, 'aq_parent', M)
+ if o is not M:
+ if not validate(object, object, name, o):
+ raise Unauthorized, name
+ object=o
+ continue
+
+ t = get(object, '__bobo_traverse__', N)
+ if t is not N:
+ o=t(REQUEST, name)
+
+ container = None
+ if has(o, 'im_self'):
+ container = o.im_self
+ elif (has(get(object, 'aq_base', object), name)
+ and get(object, name) == o):
+ container = object
+ if not validate(object, container, name, o):
+ raise Unauthorized, name
+ else:
+ # Try an attribute.
+ o = get(object, name, M)
+ # print '...', (object, name, M, o)
+ if o is not M:
+ # Check access to the attribute.
+ if has(object, 'aq_acquire'):
+ object.aq_acquire(
+ name, validate2, validate)
+ else:
+ if not validate(object, object, name, o):
+ raise Unauthorized, name
+ else:
+ # Try an item.
+ try:
+ # XXX maybe in Python 2.2 we can just check whether
+ # the object has the attribute "__getitem__"
+ # instead of blindly catching exceptions.
+ # print 'Try an item', (object, name)
+ o = object[name]
+ except AttributeError, exc:
+ if str(exc).find('__getitem__') >= 0:
+ # The object does not support the item interface.
+ # Try to re-raise the original attribute error.
+ # XXX I think this only happens with
+ # ExtensionClass instances.
+ get(object, name)
+ raise
+ except TypeError, exc:
+ if str(exc).find('unsubscriptable') >= 0:
+ # The object does not support the item interface.
+ # Try to re-raise the original attribute error.
+ # XXX This is sooooo ugly.
+ get(object, name)
+ raise
+ else:
+ # Check access to the item.
+ if not validate(object, object, name, o):
+ raise Unauthorized, name
+ #print '... object is now', `o`
+ object = o
+
+ return object
+
+
+def validate2(orig, inst, name, v, real_validate):
+ if not real_validate(orig, inst, name, v):
+ raise Unauthorized, name
+ return 1
+
diff --git a/PageTemplates/PageTemplate.py b/PageTemplates/PageTemplate.py
--- /dev/null
@@ -0,0 +1,207 @@
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+#
+##############################################################################
+"""Page Template module
+
+HTML- and XML-based template objects using TAL, TALES, and METAL.
+"""
+
+__version__='$Revision: 1.1 $'[11:-2]
+
+import sys
+
+from TAL.TALParser import TALParser
+from TAL.HTMLTALParser import HTMLTALParser
+from TAL.TALGenerator import TALGenerator
+from TAL.TALInterpreter import TALInterpreter
+from Expressions import getEngine
+from string import join, strip, rstrip, split, replace, lower, find
+from cStringIO import StringIO
+from ExtensionClass import Base
+from ComputedAttribute import ComputedAttribute
+
+
+class PageTemplate(Base):
+ "Page Templates using TAL, TALES, and METAL"
+
+ 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 macros(self):
+ return self.pt_macros()
+ macros = ComputedAttribute(macros, 1)
+
+ def pt_edit(self, text, content_type):
+ if content_type:
+ self.content_type = str(content_type)
+ if hasattr(text, 'read'):
+ text = text.read()
+ self.write(text)
+
+ def pt_getContext(self):
+ c = {'template': self,
+ 'options': {},
+ 'nothing': None,
+ 'request': None,
+ 'modules': ModuleImporter,
+ }
+ parent = getattr(self, 'aq_parent', None)
+ if parent is not None:
+ c['here'] = parent
+ c['container'] = self.aq_inner.aq_parent
+ while parent is not None:
+ self = parent
+ parent = getattr(self, 'aq_parent', None)
+ c['root'] = self
+ return c
+
+ def pt_render(self, source=0, extra_context={}):
+ """Render this Page Template"""
+ if not self._v_cooked:
+ self._cook()
+
+ __traceback_supplement__ = (PageTemplateTracebackSupplement, self)
+
+ if self._v_errors:
+ raise PTRuntimeError, 'Page Template %s has errors.' % self.id
+ output = StringIO()
+ c = self.pt_getContext()
+ c.update(extra_context)
+
+ TALInterpreter(self._v_program, self._v_macros,
+ getEngine().getContext(c),
+ output,
+ tal=not source, strictinsert=0)()
+ return output.getvalue()
+
+ def __call__(self, *args, **kwargs):
+ if not kwargs.has_key('args'):
+ kwargs['args'] = args
+ return self.pt_render(extra_context={'options': kwargs})
+
+ def pt_errors(self):
+ if not self._v_cooked:
+ self._cook()
+ err = self._v_errors
+ if err:
+ return err
+ if not self.expand: return
+ try:
+ self.pt_render(source=1)
+ except:
+ return ('Macro expansion failed', '%s: %s' % sys.exc_info()[:2])
+
+ def pt_warnings(self):
+ if not self._v_cooked:
+ self._cook()
+ return self._v_warnings
+
+ 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.' % self.id
+ return self._v_macros
+
+ def pt_source_file(self):
+ return None # Unknown.
+
+ def write(self, text):
+ assert type(text) is type('')
+ if text[:len(self._error_start)] == self._error_start:
+ errend = find(text, '-->')
+ if errend >= 0:
+ text = text[errend + 4:]
+ if self._text != text:
+ self._text = text
+ self._cook()
+
+ def read(self):
+ if not self._v_cooked:
+ self._cook()
+ if not self._v_errors:
+ if not self.expand:
+ return self._text
+ try:
+ return self.pt_render(source=1)
+ except:
+ return ('%s\n Macro expansion failed\n %s\n-->\n%s' %
+ (self._error_start, "%s: %s" % sys.exc_info()[:2],
+ self._text) )
+
+ return ('%s\n %s\n-->\n%s' % (self._error_start,
+ join(self._v_errors, '\n '),
+ self._text))
+
+ 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):
+ if not hasattr(getattr(self, 'aq_base', self), 'is_html'):
+ return self.content_type == 'text/html'
+ return self.is_html
+
+class _ModuleImporter:
+ def __getitem__(self, module):
+ mod = __import__(module)
+ path = split(module, '.')
+ 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
+
diff --git a/PageTemplates/PathIterator.py b/PageTemplates/PathIterator.py
--- /dev/null
@@ -0,0 +1,48 @@
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+#
+##############################################################################
+
+"""Path Iterator
+
+A TALES Iterator with the ability to use first() and last() on
+subpaths of elements.
+"""
+
+__version__='$Revision: 1.1 $'[11:-2]
+
+import TALES
+from Expressions import restrictedTraverse, Undefs, getSecurityManager
+from string import split
+
+class Iterator(TALES.Iterator):
+ def __bobo_traverse__(self, REQUEST, name):
+ if name in ('first', 'last'):
+ path = REQUEST['TraversalRequestNameStack']
+ names = list(path)
+ 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 = split(name, '/')
+ name = filter(None, name)
+ securityManager = getSecurityManager()
+ try:
+ ob1 = restrictedTraverse(ob1, name, securityManager)
+ ob2 = restrictedTraverse(ob2, name, securityManager)
+ except Undefs:
+ return 0
+ return ob1 == ob2
+
diff --git a/PageTemplates/PythonExpr.py b/PageTemplates/PythonExpr.py
--- /dev/null
@@ -0,0 +1,82 @@
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+#
+##############################################################################
+
+"""Generic Python Expression Handler
+"""
+
+__version__='$Revision: 1.1 $'[11:-2]
+
+from TALES import CompilerError
+from string import strip, split, join, replace, lstrip
+from sys import exc_info
+
+class getSecurityManager:
+ '''Null security manager'''
+ def validate(self, *args, **kwargs):
+ return 1
+ addContext = removeContext = validateValue = validate
+
+class PythonExpr:
+ def __init__(self, name, expr, engine):
+ self.expr = expr = replace(strip(expr), '\n', ' ')
+ try:
+ d = {}
+ exec 'def f():\n return %s\n' % strip(expr) 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):
+ # Bind template variables
+ names = {}
+ vars = econtext.vars
+ getType = econtext._engine.getTypes().get
+ for vname in self._f_varnames:
+ has, val = vars.has_get(vname)
+ if not has:
+ has = val = getType(vname)
+ if has:
+ val = ExprTypeProxy(vname, val, econtext)
+ if has:
+ names[vname] = 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._engine)(self._econtext)
+
diff --git a/PageTemplates/README.txt b/PageTemplates/README.txt
--- /dev/null
+++ b/PageTemplates/README.txt
@@ -0,0 +1,7 @@
+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.
diff --git a/PageTemplates/TALES.py b/PageTemplates/TALES.py
--- /dev/null
+++ b/PageTemplates/TALES.py
@@ -0,0 +1,285 @@
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+#
+##############################################################################
+"""TALES
+
+An implementation of a generic TALES engine
+"""
+
+__version__='$Revision: 1.1 $'[11:-2]
+
+import re, sys, ZTUtils
+from MultiMapping import MultiMapping
+
+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()
+
+_marker = []
+
+class SafeMapping(MultiMapping):
+ '''Mapping with security declarations and limited method exposure.
+
+ Since it subclasses MultiMapping, this class can be used to wrap
+ one or more mapping objects. Restricted Python code will not be
+ able to mutate the SafeMapping or the wrapped mappings, but will be
+ able to read any value.
+ '''
+ __allow_access_to_unprotected_subobjects__ = 1
+ push = pop = None
+
+ _push = MultiMapping.push
+ _pop = MultiMapping.pop
+
+ def has_get(self, key, _marker=[]):
+ v = self.get(key, _marker)
+ return v is not _marker, v
+
+class Iterator(ZTUtils.Iterator):
+ def __init__(self, name, seq, context):
+ ZTUtils.Iterator.__init__(self, seq)
+ self.name = name
+ self._context = context
+
+ def next(self):
+ if ZTUtils.Iterator.next(self):
+ self._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, engine, contexts):
+ self._engine = engine
+ 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 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._engine.Iterator(name, (), self)
+ it = self._engine.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._engine.compile(expression)
+ __traceback_supplement__ = (
+ TALESTracebackSupplement, self, expression)
+ v = expression(self)
+ return v
+
+ evaluateValue = evaluate
+
+ def evaluateBoolean(self, expr):
+ return not not self.evaluate(expr)
+
+ def evaluateText(self, expr, None=None):
+ text = self.evaluate(expr)
+ if text is Default or text is None:
+ return text
+ return str(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
+
+
+
+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
+ data = self.context.contexts.copy()
+ s = pprint.pformat(data)
+ if not as_html:
+ return ' - Names:\n %s' % string.replace(s, '\n', '\n ')
+ else:
+ from cgi import escape
+ return '<b>Names:</b><pre>%s</pre>' % (escape(s))
+ return None
+
+
+
+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`)
+
diff --git a/PageTemplates/__init__.py b/PageTemplates/__init__.py
--- /dev/null
@@ -0,0 +1,28 @@
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+#
+##############################################################################
+__doc__='''Package wrapper for Page Templates
+
+This wrapper allows the Page Template modules to be segregated in a
+separate package.
+
+$Id: __init__.py,v 1.1 2002-08-30 08:27:34 richard Exp $'''
+__version__='$$'[11:-2]
+
+
+# Placeholder for Zope Product data
+misc_ = {}
+
+def initialize(context):
+ # Import lazily, and defer initialization to the module
+ import ZopePageTemplate
+ ZopePageTemplate.initialize(context)