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.
18 Modified for Roundup 0.5 release:
20 - changed imports to import from roundup.cgi
22 """
24 __version__='$Revision: 1.2 $'[11:-2]
26 import sys
28 from roundup.cgi.TAL.TALParser import TALParser
29 from roundup.cgi.TAL.HTMLTALParser import HTMLTALParser
30 from roundup.cgi.TAL.TALGenerator import TALGenerator
31 from roundup.cgi.TAL.TALInterpreter import TALInterpreter
32 from Expressions import getEngine
33 from string import join, strip, rstrip, split, replace, lower, find
34 from cStringIO import StringIO
35 from ComputedAttribute import ComputedAttribute
37 class PageTemplate:
38 "Page Templates using TAL, TALES, and METAL"
40 content_type = 'text/html'
41 expand = 0
42 _v_errors = ()
43 _v_warnings = ()
44 _v_program = None
45 _v_macros = None
46 _v_cooked = 0
47 id = '(unknown)'
48 _text = ''
49 _error_start = '<!-- Page Template Diagnostics'
51 def macros(self):
52 return self.pt_macros()
53 macros = ComputedAttribute(macros, 1)
55 def pt_edit(self, text, content_type):
56 if content_type:
57 self.content_type = str(content_type)
58 if hasattr(text, 'read'):
59 text = text.read()
60 self.write(text)
62 def pt_getContext(self):
63 c = {'template': self,
64 'options': {},
65 'nothing': None,
66 'request': None,
67 'modules': ModuleImporter,
68 }
69 parent = getattr(self, 'aq_parent', None)
70 if parent is not None:
71 c['here'] = parent
72 c['container'] = self.aq_inner.aq_parent
73 while parent is not None:
74 self = parent
75 parent = getattr(self, 'aq_parent', None)
76 c['root'] = self
77 return c
79 def pt_render(self, source=0, extra_context={}):
80 """Render this Page Template"""
81 if not self._v_cooked:
82 self._cook()
84 __traceback_supplement__ = (PageTemplateTracebackSupplement, self)
86 if self._v_errors:
87 raise PTRuntimeError, 'Page Template %s has errors.' % self.id
88 output = StringIO()
89 c = self.pt_getContext()
90 c.update(extra_context)
92 TALInterpreter(self._v_program, self._v_macros,
93 getEngine().getContext(c),
94 output,
95 tal=not source, strictinsert=0)()
96 return output.getvalue()
98 def __call__(self, *args, **kwargs):
99 if not kwargs.has_key('args'):
100 kwargs['args'] = args
101 return self.pt_render(extra_context={'options': kwargs})
103 def pt_errors(self):
104 if not self._v_cooked:
105 self._cook()
106 err = self._v_errors
107 if err:
108 return err
109 if not self.expand: return
110 try:
111 self.pt_render(source=1)
112 except:
113 return ('Macro expansion failed', '%s: %s' % sys.exc_info()[:2])
115 def pt_warnings(self):
116 if not self._v_cooked:
117 self._cook()
118 return self._v_warnings
120 def pt_macros(self):
121 if not self._v_cooked:
122 self._cook()
123 if self._v_errors:
124 __traceback_supplement__ = (PageTemplateTracebackSupplement, self)
125 raise PTRuntimeError, 'Page Template %s has errors.' % self.id
126 return self._v_macros
128 def pt_source_file(self):
129 return None # Unknown.
131 def write(self, text):
132 assert type(text) is type('')
133 if text[:len(self._error_start)] == self._error_start:
134 errend = find(text, '-->')
135 if errend >= 0:
136 text = text[errend + 4:]
137 if self._text != text:
138 self._text = text
139 self._cook()
141 def read(self):
142 if not self._v_cooked:
143 self._cook()
144 if not self._v_errors:
145 if not self.expand:
146 return self._text
147 try:
148 return self.pt_render(source=1)
149 except:
150 return ('%s\n Macro expansion failed\n %s\n-->\n%s' %
151 (self._error_start, "%s: %s" % sys.exc_info()[:2],
152 self._text) )
154 return ('%s\n %s\n-->\n%s' % (self._error_start,
155 join(self._v_errors, '\n '),
156 self._text))
158 def _cook(self):
159 """Compile the TAL and METAL statments.
161 Cooking must not fail due to compilation errors in templates.
162 """
163 source_file = self.pt_source_file()
164 if self.html():
165 gen = TALGenerator(getEngine(), xml=0, source_file=source_file)
166 parser = HTMLTALParser(gen)
167 else:
168 gen = TALGenerator(getEngine(), source_file=source_file)
169 parser = TALParser(gen)
171 self._v_errors = ()
172 try:
173 parser.parseString(self._text)
174 self._v_program, self._v_macros = parser.getCode()
175 except:
176 self._v_errors = ["Compilation failed",
177 "%s: %s" % sys.exc_info()[:2]]
178 self._v_warnings = parser.getWarnings()
179 self._v_cooked = 1
181 def html(self):
182 if not hasattr(getattr(self, 'aq_base', self), 'is_html'):
183 return self.content_type == 'text/html'
184 return self.is_html
186 class _ModuleImporter:
187 def __getitem__(self, module):
188 mod = __import__(module)
189 path = split(module, '.')
190 for name in path[1:]:
191 mod = getattr(mod, name)
192 return mod
194 ModuleImporter = _ModuleImporter()
196 class PTRuntimeError(RuntimeError):
197 '''The Page Template has template errors that prevent it from rendering.'''
198 pass
201 class PageTemplateTracebackSupplement:
202 #__implements__ = ITracebackSupplement
204 def __init__(self, pt):
205 self.object = pt
206 w = pt.pt_warnings()
207 e = pt.pt_errors()
208 if e:
209 w = list(w) + list(e)
210 self.warnings = w