[Zope3-checkins] CVS: Zope3/src/zope/pagetemplate - __init__.py:1.1.2.1 engine.py:1.1.2.1 expressions.py:1.1.2.1 interfaces.py:1.1.2.1 iterator.py:1.1.2.1 pagetemplate.py:1.1.2.1 pagetemplatefile.py:1.1.2.1 pythonexpr.py:1.1.2.1 readme.txt:1.1.2.1 safemapping.py:1.1.2.1 tales.py:1.1.2.1

Jim Fulton jim@zope.com
Mon, 23 Dec 2002 14:33:00 -0500


Update of /cvs-repository/Zope3/src/zope/pagetemplate
In directory cvs.zope.org:/tmp/cvs-serv19908/zope/pagetemplate

Added Files:
      Tag: NameGeddon-branch
	__init__.py engine.py expressions.py interfaces.py iterator.py 
	pagetemplate.py pagetemplatefile.py pythonexpr.py readme.txt 
	safemapping.py tales.py 
Log Message:
Initial renaming before debugging

=== Added File Zope3/src/zope/pagetemplate/__init__.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
# 
##############################################################################
"""Zope 3 compatible ZTUtils."""

from zope.pagetemplate.iterator import Iterator


=== Added File Zope3/src/zope/pagetemplate/engine.py ===
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
# 
##############################################################################
"""Expression engine configuration and registration.

Each expression engine can have its own expression types and base names.

$Id: engine.py,v 1.1.2.1 2002/12/23 19:32:58 jim Exp $
"""

from zope.pagetemplate.tales import ExpressionEngine, RegistrationError
from zope.pagetemplate.expressions import PathExpr, StringExpr, NotExpr, DeferExpr
from zope.pagetemplate.expressions import SimpleModuleImporter
from zope.pagetemplate.pythonexpr import PythonExpr

def Engine():
    e = ExpressionEngine()
    reg = e.registerType
    for pt in PathExpr._default_type_names:
        reg(pt, PathExpr)
    reg('string', StringExpr)
    reg('python', PythonExpr)
    reg('not', NotExpr)
    reg('defer', DeferExpr)
    e.registerBaseName('modules', SimpleModuleImporter())
    return e

Engine = Engine()



=== Added File Zope3/src/zope/pagetemplate/expressions.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
# 
##############################################################################
"""Basic Page Template expression types.

$Id: expressions.py,v 1.1.2.1 2002/12/23 19:32:58 jim Exp $
"""
__metaclass__ = type # All classes are new style when run with Python 2.2+

import re, sys
from types import StringTypes

from zope.pagetemplate.tales import ExpressionEngine, CompilerError, RegistrationError
from zope.pagetemplate.tales import _valid_name, _parse_expr, NAME_RE, Undefined
from zope.pagetemplate.pythonexpr import PythonExpr

Undefs = (Undefined, AttributeError, KeyError,
          TypeError, IndexError)

_marker = object()

def simpleTraverse(object, path_items, econtext):
    """Traverses a sequence of names, first trying attributes then items.
    """

    for name in path_items:
        next = getattr(object, name, _marker)
        if next is not _marker:
            object = next
        elif hasattr(object, '__getitem__'):
            object = object[name]
        else:
            raise NameError, name
    return object


class SubPathExpr:
    def __init__(self, path, traverser):
        self._path = path = str(path).strip().split('/')
        self._base = base = path.pop(0)
        self._traverser = traverser
        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):
        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, StringTypes):
                    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)
        base = self._base
        if base == 'CONTEXTS':  # Special base name
            ob = econtext.contexts
        else:
            ob = vars[base]
        if isinstance(ob, DeferWrapper):
            ob = ob()
        if path:
            ob = self._traverser(ob, path, econtext)
        return ob



class PathExpr:
    """One or more subpath expressions, separated by '|'.
    """

    # _default_type_names contains the expression type names this
    # class is usually registered for.
    _default_type_names = (
        'standard',
        'path',
        'exists',
        'nocall',
        )

    def __init__(self, name, expr, engine, traverser=simpleTraverse):
        self._s = expr
        self._name = name
        paths = expr.split('|')
        self._subexprs = []
        add = self._subexprs.append
        for i in range(len(paths)):
            path = paths[i].lstrip()
            if _parse_expr(path):
                # This part is the start of another expression type,
                # so glue it back together and compile it.
                add(engine.compile('|'.join(paths[i:]).lstrip()))
                break
            add(SubPathExpr(path, traverser)._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):
        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':
            return ob

        # Call the object if it is callable.
        if hasattr(ob, '__call__'):
            return ob()
        return ob

    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 '<PathExpr %s:%s>' % (self._name, `self._s`)



_interp = re.compile(r'\$(%(n)s)|\${(%(n)s(?:/[^}]*)*)}' % {'n': NAME_RE})

class StringExpr:
    def __init__(self, name, expr, engine):
        self._s = expr
        if '%' in expr:
            expr = expr.replace('%', '%%')
        self._vars = vars = []
        if '$' in expr:
            # Use whatever expr type is registered as "path".
            path_type = engine.getTypes()['path']
            parts = []
            for exp in expr.split('$$'):
                if parts: parts.append('$')
                m = _interp.search(exp)
                while m is not None:
                    parts.append(exp[:m.start()])
                    parts.append('%s')
                    vars.append(path_type(
                        '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)
            vvals.append(v)
        return self._expr % tuple(vvals)

    def __str__(self):
        return 'string expression (%s)' % `self._s`

    def __repr__(self):
        return '<StringExpr %s>' % `self._s`


class NotExpr:
    def __init__(self, name, expr, engine):
        self._s = expr = expr.lstrip()
        self._c = engine.compile(expr)

    def __call__(self, econtext):
        return int(not econtext.evaluateBoolean(self._c))

    def __repr__(self):
        return '<NotExpr %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 = expr.lstrip()
        self._c = compiler.compile(expr)
        
    def __call__(self, econtext):
        return DeferWrapper(self._c, econtext)

    def __repr__(self):
        return '<DeferExpr %s>' % `self._s`


class SimpleModuleImporter:
    """Minimal module importer with no security."""
    def __getitem__(self, module):
        mod = __import__(module)
        path = module.split('.')
        for name in path[1:]:
            mod = getattr(mod, name)
        return mod



=== Added File Zope3/src/zope/pagetemplate/interfaces.py ===
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
# 
##############################################################################
"""Interface that describes the 'macros' attribute of a PageTemplate.

$Id: interfaces.py,v 1.1.2.1 2002/12/23 19:32:58 jim Exp $
"""
from zope.interface import Interface
from zope.interface.element import Attribute

class IMacrosAttribute(Interface):

    macros = Attribute("An object that implements the __getitem__ "
                       "protocol, containing page template macros.")



=== Added File Zope3/src/zope/pagetemplate/iterator.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
# 
##############################################################################
__doc__='''Iterator class

$Id: iterator.py,v 1.1.2.1 2002/12/23 19:32:58 jim Exp $'''
__version__='$Revision: 1.1.2.1 $'[11:-2]

class Iterator:
    '''Simple Iterator class'''

    __allow_access_to_unprotected_subobjects__ = 1
    
    def __init__(self, seq):
        self.seq = seq
        self.nextIndex = 0

    def next(self):
        i = self.nextIndex
        try:
            self.seq[i]
        except IndexError:
            return 0
        self.index = i
        self.nextIndex = i+1
        return 1

    def number(self): return self.nextIndex

    def even(self): return not self.index % 2

    def odd(self): return self.index % 2

    def letter(self, base=ord('a'), radix=26):
        index = self.index
        s = ''
        while 1:
            index, off = divmod(index, radix)
            s = chr(base + off) + s
            if not index: return s

    def Letter(self):
        return self.letter(base=ord('A'))

    def Roman(self, rnvalues=(
                    (1000,'M'),(900,'CM'),(500,'D'),(400,'CD'),
                    (100,'C'),(90,'XC'),(50,'L'),(40,'XL'),
                    (10,'X'),(9,'IX'),(5,'V'),(4,'IV'),(1,'I')) ):
        n = self.index + 1
        s = ''
        for v, r in rnvalues:
            rct, n = divmod(n, v)
            s = s + r * rct
        return s

    def roman(self):
        return self.Roman.lower()

    def start(self): return self.nextIndex == 1

    def end(self):
        try: self.seq[self.nextIndex]
        except IndexError: return 1
        return 0

    def item(self):
        return self.seq[self.index]

    def length(self):
        return len(self.seq)



=== Added File Zope3/src/zope/pagetemplate/pagetemplate.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Page Template module

HTML- and XML-based template objects using TAL, TALES, and METAL.

$Id: pagetemplate.py,v 1.1.2.1 2002/12/23 19:32:58 jim Exp $
"""
__metaclass__ = type # All classes are new style when run with Python 2.2+

import sys
from zope.tal.talparser import TALParser
from zope.tal.htmltalparser import HTMLTALParser
from zope.tal.talgenerator import TALGenerator
from zope.tal.talinterpreter import TALInterpreter
from zope.pagetemplate.engine import Engine
from zope.pagetemplate.interfaces import IMacrosAttribute
# Don't use cStringIO here!  It's not unicode aware.
from StringIO import StringIO


class MacroCollection:
    def __get__(self, parent, type=None):
        parent._cook_check()
        return parent._v_macros


_default_options = {}

class PageTemplate:
    """Page Templates using TAL, TALES, and METAL.

    Subclassing
    -----------

    The following methods have certain internal responsibilities.

    pt_getContext(**keywords)
        Should ignore keyword arguments that it doesn't care about,
        and construct the namespace passed to the TALES expression
        engine.  This method is free to use the keyword arguments it
        receives.

    pt_render(namespace, source=0)
        Responsible the TAL interpreter to perform the rendering.  The
        namespace argument is a mapping which defines the top-level
        namespaces passed to the TALES expression engine.

    __call__(*args, **keywords)
        Calls pt_getContext() to construct the top-level namespace
        passed to the TALES expression engine, then calls pt_render()
        to perform the rendering.
    """

    # XXX this breaks something in the Zope3 views registries.
    # Temporarily removed. SteveA 2002-10-22
    #__implements__ = IMacrosAttribute

    content_type = 'text/html'
    expand = 1
    _v_errors = ()
    _v_warnings = ()
    _v_program = None
    _v_macros = None
    _v_cooked = 0
    _text = ''
    _engine_name = 'default'
    _error_start = '<!-- Page Template Diagnostics'

    macros = MacroCollection()

    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, args=(), options=_default_options, **ignored):
        rval = {'template': self,
                'options': options,
                'args': args,
                'nothing': None,
                }
        rval.update(self.pt_getEngine().getBaseNames())
        return rval

    def __call__(self, *args, **kwargs):
        return self.pt_render(self.pt_getContext(args, kwargs))

    pt_getEngineContext = Engine.getContext

    def pt_getEngine(self):
        return Engine

    def pt_render(self, namespace, source=0):
        """Render this Page Template"""
        self._cook_check()
        __traceback_supplement__ = (PageTemplateTracebackSupplement,
                                    self, namespace)
        if self._v_errors:
            raise PTRuntimeError(str(self._v_errors))
        output = StringIO(u'')

        context = self.pt_getEngineContext(namespace)
        TALInterpreter(self._v_program, self._v_macros,
                       context, output, tal=not source, strictinsert=0)()
        return output.getvalue()

    def pt_errors(self, namespace):
        self._cook_check()
        err = self._v_errors
        if err:
            return err
        try:
            self.pt_render(namespace, source=1)
        except:
            return ('Macro expansion failed', '%s: %s' % sys.exc_info()[:2])

    def pt_warnings(self):
        self._cook_check()
        return self._v_warnings

    def write(self, text):
        assert isinstance(text, str)
        if text.startswith(self._error_start):
            errend = text.find('-->')
            if errend >= 0:
                text = text[errend + 4:]
        if self._text != text:
            self._text = text
        # XXX can this be done only if we changed self._text?
        self._cook()

    def read(self):
        """Gets the source, sometimes with macros expanded."""
        self._cook_check()
        if not self._v_errors:
            if not self.expand:
                return self._text
            try:
                # XXX not clear how this ever gets called, but the
                # first arg to pt_render() needs to change if it ever does.
                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,
                                      '\n'.join(self._v_errors),
                                      self._text))

    def pt_source_file(self):
        """To be overridden."""
        return None

    def _cook_check(self):
        if not self._v_cooked:
            self._cook()

    def _cook(self):
        """Compile the TAL and METAL statments.

        Cooking must not fail due to compilation errors in templates.
        """
        engine = self.pt_getEngine()
        source_file = self.pt_source_file()
        if self.html():
            gen = TALGenerator(engine, xml=0, source_file=source_file)
            parser = HTMLTALParser(gen)
        else:
            gen = TALGenerator(engine, 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(self, 'is_html'):
            return self.content_type == 'text/html'
        return self.is_html



class PTRuntimeError(RuntimeError):
    '''The Page Template has template errors that prevent it from rendering.'''
    pass


class PageTemplateTracebackSupplement:
    #__implements__ = ITracebackSupplement

    def __init__(self, pt, namespace):
        self.manageable_object = pt
        try:
            w = pt.pt_warnings()
        except: # We're already trying to report an error, don't make another.
            w = ()
        e = pt.pt_errors(namespace)
        if e:
            w = list(w) + list(e)
        self.warnings = w


=== Added File Zope3/src/zope/pagetemplate/pagetemplatefile.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
# 
##############################################################################
"""Filesystem Page Template module

Zope object encapsulating a Page Template from the filesystem.
"""

__metaclass__ = type

__version__ = '$Revision: 1.1.2.1 $'[11:-2]

import os, sys
import logging

from zope.pagetemplate.pagetemplate import PageTemplate

def package_home(gdict):
    filename = gdict["__file__"]
    return os.path.dirname(filename)

class PageTemplateFile(PageTemplate):
    "Zope wrapper for filesystem Page Template using TAL, TALES, and METAL"

    _v_last_read = 0

    def __init__(self, filename, _prefix=None):
        if not isinstance(_prefix, str):
            if _prefix is None:
                _prefix = sys._getframe(1).f_globals
            _prefix = package_home(_prefix)
            
        self.filename = os.path.join(_prefix, filename)

    def _cook_check(self):
        if self._v_last_read and not __debug__:
            return
        __traceback_info__ = self.filename
        try:
            mtime = os.path.getmtime(self.filename)
        except OSError:
            mtime = 0
        if self._v_program is not None and mtime == self._v_last_read:
            return
        self.pt_edit(open(self.filename), None)
        self._cook()
        if self._v_errors:
            logging.error('PageTemplateFile: Error in template: %s',
                '\n'.join(self._v_errors))
            return
        self._v_last_read = mtime

    def document_src(self, REQUEST=None):
        """Return expanded document source."""

        if REQUEST is not None:
            REQUEST.response.setHeader('Content-Type', self.content_type)
        return self.read()

    def pt_source_file(self):
        return self.filename

    def __getstate__(self):
        raise TypeError("non-picklable object")


=== Added File Zope3/src/zope/pagetemplate/pythonexpr.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
# 
##############################################################################
"""Generic Python Expression Handler"""

__version__ = '$Revision: 1.1.2.1 $'[11:-2]

class PythonExpr:
    def __init__(self, name, expr, engine):
        text = expr.replace('\n', ' ').strip()
        self.text = text
        # The next line can legally raise SyntaxError.
        self._code = code = compile(text, '<string>', 'eval')
        self._varnames = code.co_names

    def _bind_used_names(self, econtext, builtins):
        # Bind template variables
        names = {}
        vars = econtext.vars
        marker = self
        for vname in self._varnames:
            val = vars.get(vname, marker)
            if val is not marker:
                names[vname] = val
            elif vname not in builtins:
                # Fall back to using expression types as variable values.
                val = econtext._engine.getTypes().get(vname, marker)
                if val is not marker:
                    val = ExprTypeProxy(vname, val, econtext)
                    names[vname] = val

        names['__builtins__'] = builtins
        return names

    def __call__(self, econtext):
        __traceback_info__ = self.text
        vars = self._bind_used_names(econtext, __builtins__)
        return eval(self._code, vars)

    def __str__(self):
        return 'Python expression "%s"' % self.text

    def __repr__(self):
        return '<PythonExpr %s>' % self.text


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)


=== Added File Zope3/src/zope/pagetemplate/readme.txt ===
Page Templates

  Introduction

     Page Templates provide an elegant templating mechanism that
     achieves a clean separation of presentation and application 
     logic while allowing for designers to work with templates
     in their visual editing tools (FrontPage, Dreamweaver, GoLive,
      etc.)

     This document focuses on usage of Page Templates outside of
     a Zope context, it does *not* explain how to write page templates
     as there are several resources on the web which do so.

  Dependencies
  
    Zope3 Package Dependencies

      - TAL (Template Attribute Language)

      - Interface 

      - ZTUtils (batching utilities for zpt)

      - The standard logging package ("logging") from Python 2.3.

  Simple Usage

    Using PageTemplates outside of Zope3 is very easy and straight
    forward. a quick example::
    
      > python
      >> from Zope.PageTemplate.PageTemplateFile import PageTemplateFile
      >> my_pt = PageTemplateFile('hello_world.pt')
      >> my_pt()
         u'<html><body>Hello World</body></html>'

  Setting Up Contexts
  
    Rendering a page template without binding data to is not very
    interesting. By default keyword arguments you pass in page
    templates appear in the options namespace. 

    pt_getContext(**keywords)
        Should ignore keyword arguments that it doesn't care about,
        and construct the namespace passed to the TALES expression
        engine.  This method is free to use the keyword arguments it
        receives.

    pt_render(namespace, source=0)
        Responsible the TAL interpreter to perform the rendering.  The
        namespace argument is a mapping which defines the top-level
        namespaces passed to the TALES expression engine.

  Narrative ()

  Narrative (Subclassing PageTemplates)

    Lets say we want to alter page templates such that keyword
    arguments appear as top level items in the namespace. we can 
    subclass page template and alter the default behavior of 
    pt_getContext to add them in::
          
      from Zope.PageTemplate.PageTemplate import PageTemplate

      class mypt(PageTemplate):
          def pt_getContext(self, args=(), options={}, **kw):
             rval = PageTemplate.pt_getContext(self, args=args)
             options.update(rval)
             return options

      class foo:
          def getContents(self): return 'hi'

    So now we can bind objects in a more arbitrary fashion, like
    the following::

      template = """    
      <html>
      <body>
      <b tal:replace="das_object/getContents">Good Stuff Here</b>
      </body>
      </html>
      """

      pt = mypt()
      pt.write(template)
      pt(das_object=foo())

  Author

    Kapil Thangavelu <hazmat at objectrealms.net>


=== Added File Zope3/src/zope/pagetemplate/safemapping.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
# 
##############################################################################
"""Simple variation of MultiMapping used to support layers of variable
declarations in TAL."""


class SafeMapping:
    def __init__(self, *dicts):
        self._mappings = list(dicts)
        self._mappings.reverse()

    def __getitem__(self, key):
        for d in self._mappings:
            if key in d:
                return d[key]
        raise KeyError, key

    def __contains__(self, key):
        for d in self._mappings:
            if key in d:
                return 1
        return 0

    has_key = __contains__

    def get(self, key, default=None):
        for d in self._mappings:
            if key in d:
                return d[key]
        return default

    def _push(self, dict):
        self._mappings.insert(0, dict)

    def _pop(self, count=1):
        del self._mappings[:count]


=== Added File Zope3/src/zope/pagetemplate/tales.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""TALES

An implementation of a generic TALES engine
"""
__metaclass__ = type # All classes are new style when run with Python 2.2+

__version__ = '$Revision: 1.1.2.1 $'[11:-2]

import re
import sys
from types import StringType, StringTypes

import zope.pagetemplate
from zope.pagetemplate.safemapping import SafeMapping

from zope.tal.interfaces import ITALESCompiler, ITALESEngine, ITALESErrorInfo


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 evaluation"""

class Undefined(TALESError):
    '''Exception raised on traversal of an undefined path'''

class CompilerError(Exception):
    '''TALES Compiler Error'''

class RegistrationError(Exception):
    '''Expression type or base name registration Error'''


_default = object()

_marker = object()


class Iterator(Zope.ZTUtils.Iterator):
    def __init__(self, name, seq, context):
        Zope.ZTUtils.Iterator.__init__(self, seq)
        self.name = name
        self._context = context

    def __iter__(self):
        return self

    def next(self):
        if Zope.ZTUtils.Iterator.next(self):
            self._context.setLocal(self.name, self.seq[self.index])
            return 1
        return 0



class ErrorInfo:
    """Information about an exception passed to an on-error handler."""

    __implements__ = ITALESErrorInfo

    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 ExpressionEngine:
    '''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.
    '''

    __implements__ = ITALESCompiler

    def __init__(self):
        self.types = {}
        self.base_names = {}
        self.iteratorFactory = Iterator

    def registerType(self, name, handler):
        if not _valid_name(name):
            raise RegistrationError, (
                'Invalid expression type name "%s".' % name)
        types = self.types
        if name in types:
            raise RegistrationError, (
                'Multiple registrations for Expression type "%s".' %
                name)
        types[name] = handler

    def getTypes(self):
        return self.types

    def registerBaseName(self, name, object):
        if not _valid_name(name):
            raise RegistrationError, 'Invalid base name "%s".' % name
        base_names = self.base_names
        if name in base_names:
            raise RegistrationError, (
                'Multiple registrations for base name "%s".' % name)
        base_names[name] = object

    def getBaseNames(self):
        return self.base_names

    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.
    '''

    __implements__ = ITALESEngine

    _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.iteratorFactory(name, (), self)
        it = self._engine.iteratorFactory(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)
        return expression(self)

    evaluateValue = evaluate

    def evaluateBoolean(self, expr):
        return not not self.evaluate(expr)

    def evaluateText(self, expr):
        text = self.evaluate(expr)
        if text is _default or text is None:
            return text
        if not isinstance(text, StringTypes):
            text = unicode(text)
        return 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 Zope.Exceptions.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()
        if 'modules' in data:
            del data['modules']     # the list is really long and boring
        s = pprint.pformat(data)
        if not as_html:
            return '   - Names:\n      %s' % s.replace('\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`)