diff options
Diffstat (limited to 'src/zope/pagetemplate/pagetemplate.py')
-rw-r--r-- | src/zope/pagetemplate/pagetemplate.py | 212 |
1 files changed, 212 insertions, 0 deletions
diff --git a/src/zope/pagetemplate/pagetemplate.py b/src/zope/pagetemplate/pagetemplate.py new file mode 100644 index 0000000..ea26cc1 --- /dev/null +++ b/src/zope/pagetemplate/pagetemplate.py @@ -0,0 +1,212 @@ +############################################################################## +# +# 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.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. +# +############################################################################## +"""Page Template module + +HTML- and XML-based template objects using TAL, TALES, and METAL. + +$Id$ +""" +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.tales.engine import Engine +# Don't use cStringIO here! It's not unicode aware. +from StringIO import StringIO + +from zope.pagetemplate.interfaces import IPageTemplateSubclassing +from zope.interface import implements + + +_default_options = {} +_error_start = '<!-- Page Template Diagnostics' + +class PageTemplate(object): + """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=False, sourceAnnotations=False, showtal=False) + 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. + """ + + implements(IPageTemplateSubclassing) + + content_type = 'text/html' + expand = 1 + _v_errors = () + _v_program = None + _v_macros = None + _v_cooked = 0 + _text = '' + + def macros(self): + self._cook_check() + return self._v_macros + + macros = property(macros) + + 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)) + + def pt_getEngineContext(self, namespace): + return self.pt_getEngine().getContext(namespace) + + def pt_getEngine(self): + return Engine + + def pt_render(self, namespace, source=False, sourceAnnotations=False, + showtal=False): + """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, showtal=showtal, + strictinsert=0, sourceAnnotations=sourceAnnotations)() + 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 write(self, text): + # We accept both, since the text can either come from a file (and the + # parser will take care of the encoding) or from a TTW template, in + # which case we already have unicode. + assert isinstance(text, (str, unicode)) + + if text.startswith(_error_start): + errend = text.find('-->') + if errend >= 0: + text = text[errend + 3:] + if text[:1] == "\n": + text = text[1:] + if self._text != text: + self._text = text + + # Always cook on an update, even if the source is the same; + # the content-type might have changed. + self._cook() + + def read(self, request=None): + """Gets the source, sometimes with macros expanded.""" + self._cook_check() + if not self._v_errors: + if not self.expand: + return self._text + try: + # This gets called, if macro expansion is turned on. + # Note that an empty dictionary is fine for the context at + # this point, since we are not evaluating the template. + return self.pt_render(self.pt_getContext(self, request), source=1) + except: + return ('%s\n Macro expansion failed\n %s\n-->\n%s' % + (_error_start, "%s: %s" % sys.exc_info()[:2], + self._text) ) + + return ('%s\n %s\n-->\n%s' % (_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.content_type == 'text/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_cooked = 1 + + +class PTRuntimeError(RuntimeError): + '''The Page Template has template errors that prevent it from rendering.''' + pass + + +class PageTemplateTracebackSupplement(object): + #implements(ITracebackSupplement) + + def __init__(self, pt, namespace): + self.manageable_object = pt + self.warnings = [] + e = pt.pt_errors(namespace) + if e: + self.warnings.extend(e) |