1 ##############################################################################
2 #
3 # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
4 #
5 # This software is subject to the provisions of the Zope Public License,
6 # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
7 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
8 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
9 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
10 # FOR A PARTICULAR PURPOSE
11 #
12 ##############################################################################
13 """Page Template module
15 HTML- and XML-based template objects using TAL, TALES, and METAL.
16 """
18 __version__='$Revision: 1.1 $'[11:-2]
20 import sys
22 from TAL.TALParser import TALParser
23 from TAL.HTMLTALParser import HTMLTALParser
24 from TAL.TALGenerator import TALGenerator
25 from TAL.TALInterpreter import TALInterpreter
26 from Expressions import getEngine
27 from string import join, strip, rstrip, split, replace, lower, find
28 from cStringIO import StringIO
29 from ExtensionClass import Base
30 from ComputedAttribute import ComputedAttribute
33 class PageTemplate(Base):
34 "Page Templates using TAL, TALES, and METAL"
36 content_type = 'text/html'
37 expand = 0
38 _v_errors = ()
39 _v_warnings = ()
40 _v_program = None
41 _v_macros = None
42 _v_cooked = 0
43 id = '(unknown)'
44 _text = ''
45 _error_start = '<!-- Page Template Diagnostics'
47 def macros(self):
48 return self.pt_macros()
49 macros = ComputedAttribute(macros, 1)
51 def pt_edit(self, text, content_type):
52 if content_type:
53 self.content_type = str(content_type)
54 if hasattr(text, 'read'):
55 text = text.read()
56 self.write(text)
58 def pt_getContext(self):
59 c = {'template': self,
60 'options': {},
61 'nothing': None,
62 'request': None,
63 'modules': ModuleImporter,
64 }
65 parent = getattr(self, 'aq_parent', None)
66 if parent is not None:
67 c['here'] = parent
68 c['container'] = self.aq_inner.aq_parent
69 while parent is not None:
70 self = parent
71 parent = getattr(self, 'aq_parent', None)
72 c['root'] = self
73 return c
75 def pt_render(self, source=0, extra_context={}):
76 """Render this Page Template"""
77 if not self._v_cooked:
78 self._cook()
80 __traceback_supplement__ = (PageTemplateTracebackSupplement, self)
82 if self._v_errors:
83 raise PTRuntimeError, 'Page Template %s has errors.' % self.id
84 output = StringIO()
85 c = self.pt_getContext()
86 c.update(extra_context)
88 TALInterpreter(self._v_program, self._v_macros,
89 getEngine().getContext(c),
90 output,
91 tal=not source, strictinsert=0)()
92 return output.getvalue()
94 def __call__(self, *args, **kwargs):
95 if not kwargs.has_key('args'):
96 kwargs['args'] = args
97 return self.pt_render(extra_context={'options': kwargs})
99 def pt_errors(self):
100 if not self._v_cooked:
101 self._cook()
102 err = self._v_errors
103 if err:
104 return err
105 if not self.expand: return
106 try:
107 self.pt_render(source=1)
108 except:
109 return ('Macro expansion failed', '%s: %s' % sys.exc_info()[:2])
111 def pt_warnings(self):
112 if not self._v_cooked:
113 self._cook()
114 return self._v_warnings
116 def pt_macros(self):
117 if not self._v_cooked:
118 self._cook()
119 if self._v_errors:
120 __traceback_supplement__ = (PageTemplateTracebackSupplement, self)
121 raise PTRuntimeError, 'Page Template %s has errors.' % self.id
122 return self._v_macros
124 def pt_source_file(self):
125 return None # Unknown.
127 def write(self, text):
128 assert type(text) is type('')
129 if text[:len(self._error_start)] == self._error_start:
130 errend = find(text, '-->')
131 if errend >= 0:
132 text = text[errend + 4:]
133 if self._text != text:
134 self._text = text
135 self._cook()
137 def read(self):
138 if not self._v_cooked:
139 self._cook()
140 if not self._v_errors:
141 if not self.expand:
142 return self._text
143 try:
144 return self.pt_render(source=1)
145 except:
146 return ('%s\n Macro expansion failed\n %s\n-->\n%s' %
147 (self._error_start, "%s: %s" % sys.exc_info()[:2],
148 self._text) )
150 return ('%s\n %s\n-->\n%s' % (self._error_start,
151 join(self._v_errors, '\n '),
152 self._text))
154 def _cook(self):
155 """Compile the TAL and METAL statments.
157 Cooking must not fail due to compilation errors in templates.
158 """
159 source_file = self.pt_source_file()
160 if self.html():
161 gen = TALGenerator(getEngine(), xml=0, source_file=source_file)
162 parser = HTMLTALParser(gen)
163 else:
164 gen = TALGenerator(getEngine(), source_file=source_file)
165 parser = TALParser(gen)
167 self._v_errors = ()
168 try:
169 parser.parseString(self._text)
170 self._v_program, self._v_macros = parser.getCode()
171 except:
172 self._v_errors = ["Compilation failed",
173 "%s: %s" % sys.exc_info()[:2]]
174 self._v_warnings = parser.getWarnings()
175 self._v_cooked = 1
177 def html(self):
178 if not hasattr(getattr(self, 'aq_base', self), 'is_html'):
179 return self.content_type == 'text/html'
180 return self.is_html
182 class _ModuleImporter:
183 def __getitem__(self, module):
184 mod = __import__(module)
185 path = split(module, '.')
186 for name in path[1:]:
187 mod = getattr(mod, name)
188 return mod
190 ModuleImporter = _ModuleImporter()
192 class PTRuntimeError(RuntimeError):
193 '''The Page Template has template errors that prevent it from rendering.'''
194 pass
197 class PageTemplateTracebackSupplement:
198 #__implements__ = ITracebackSupplement
200 def __init__(self, pt):
201 self.object = pt
202 w = pt.pt_warnings()
203 e = pt.pt_errors()
204 if e:
205 w = list(w) + list(e)
206 self.warnings = w