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 # Modified for Roundup:
14 #
15 # 1. changed imports to import from roundup.cgi
16 # 2. removed use of ExtensionClass
17 # 3. removed use of ComputedAttribute
18 """Page Template module
20 HTML- and XML-based template objects using TAL, TALES, and METAL.
21 """
23 __version__='$Revision: 1.5 $'[11:-2]
25 import sys
27 from roundup.cgi.TAL.TALParser import TALParser
28 from roundup.cgi.TAL.HTMLTALParser import HTMLTALParser
29 from roundup.cgi.TAL.TALGenerator import TALGenerator
30 # Do not use cStringIO here! It's not unicode aware. :(
31 from roundup.cgi.TAL.TALInterpreter import TALInterpreter, FasterStringIO
32 from Expressions import getEngine
35 class PageTemplate:
36 "Page Templates using TAL, TALES, and METAL"
38 content_type = 'text/html'
39 expand = 0
40 _v_errors = ()
41 _v_warnings = ()
42 _v_program = None
43 _v_macros = None
44 _v_cooked = 0
45 id = '(unknown)'
46 _text = ''
47 _error_start = '<!-- Page Template Diagnostics'
49 def StringIO(self):
50 # Third-party products wishing to provide a full Unicode-aware
51 # StringIO can do so by monkey-patching this method.
52 return FasterStringIO()
54 def pt_edit(self, text, content_type):
55 if content_type:
56 self.content_type = str(content_type)
57 if hasattr(text, 'read'):
58 text = text.read()
59 self.write(text)
61 def pt_getContext(self):
62 c = {'template': self,
63 'options': {},
64 'nothing': None,
65 'request': None,
66 'modules': ModuleImporter,
67 }
68 parent = getattr(self, 'aq_parent', None)
69 if parent is not None:
70 c['here'] = parent
71 c['container'] = self.aq_inner.aq_parent
72 while parent is not None:
73 self = parent
74 parent = getattr(self, 'aq_parent', None)
75 c['root'] = self
76 return c
78 def pt_render(self, source=0, extra_context={}):
79 """Render this Page Template"""
80 if not self._v_cooked:
81 self._cook()
83 __traceback_supplement__ = (PageTemplateTracebackSupplement, self)
85 if self._v_errors:
86 raise PTRuntimeError, 'Page Template %s has errors.' % self.id
87 output = self.StringIO()
88 c = self.pt_getContext()
89 c.update(extra_context)
91 TALInterpreter(self._v_program, self._v_macros,
92 getEngine().getContext(c),
93 output,
94 tal=not source, strictinsert=0)()
95 return output.getvalue()
97 def __call__(self, *args, **kwargs):
98 if not kwargs.has_key('args'):
99 kwargs['args'] = args
100 return self.pt_render(extra_context={'options': kwargs})
102 def pt_errors(self):
103 if not self._v_cooked:
104 self._cook()
105 err = self._v_errors
106 if err:
107 return err
108 if not self.expand: return
109 try:
110 self.pt_render(source=1)
111 except:
112 return ('Macro expansion failed', '%s: %s' % sys.exc_info()[:2])
114 def pt_warnings(self):
115 if not self._v_cooked:
116 self._cook()
117 return self._v_warnings
119 def pt_macros(self):
120 if not self._v_cooked:
121 self._cook()
122 __traceback_supplement__ = (PageTemplateTracebackSupplement, self)
123 if self._v_errors:
124 raise PTRuntimeError, 'Page Template %s has errors.' % self.id
125 return self._v_macros
127 def __getattr__(self, name):
128 if name == 'macros':
129 return self.pt_macros()
130 raise AttributeError, name
132 def pt_source_file(self):
133 return None # Unknown.
135 def write(self, text):
136 assert type(text) is type('')
137 if text[:len(self._error_start)] == self._error_start:
138 errend = text.find('-->')
139 if errend >= 0:
140 text = text[errend + 4:]
141 if self._text != text:
142 self._text = text
143 self._cook()
145 def read(self):
146 self._cook_check()
147 if not self._v_errors:
148 if not self.expand:
149 return self._text
150 try:
151 return self.pt_render(source=1)
152 except:
153 return ('%s\n Macro expansion failed\n %s\n-->\n%s' %
154 (self._error_start, "%s: %s" % sys.exc_info()[:2],
155 self._text) )
157 return ('%s\n %s\n-->\n%s' % (self._error_start,
158 '\n '.join(self._v_errors),
159 self._text))
161 def _cook_check(self):
162 if not self._v_cooked:
163 self._cook()
165 def _cook(self):
166 """Compile the TAL and METAL statments.
168 Cooking must not fail due to compilation errors in templates.
169 """
170 source_file = self.pt_source_file()
171 if self.html():
172 gen = TALGenerator(getEngine(), xml=0, source_file=source_file)
173 parser = HTMLTALParser(gen)
174 else:
175 gen = TALGenerator(getEngine(), source_file=source_file)
176 parser = TALParser(gen)
178 self._v_errors = ()
179 try:
180 parser.parseString(self._text)
181 self._v_program, self._v_macros = parser.getCode()
182 except:
183 self._v_errors = ["Compilation failed",
184 "%s: %s" % sys.exc_info()[:2]]
185 self._v_warnings = parser.getWarnings()
186 self._v_cooked = 1
188 def html(self):
189 if not hasattr(getattr(self, 'aq_base', self), 'is_html'):
190 return self.content_type == 'text/html'
191 return self.is_html
193 class _ModuleImporter:
194 def __getitem__(self, module):
195 mod = __import__(module)
196 path = module.split('.')
197 for name in path[1:]:
198 mod = getattr(mod, name)
199 return mod
201 ModuleImporter = _ModuleImporter()
203 class PTRuntimeError(RuntimeError):
204 '''The Page Template has template errors that prevent it from rendering.'''
205 pass
208 class PageTemplateTracebackSupplement:
209 #__implements__ = ITracebackSupplement
211 def __init__(self, pt):
212 self.object = pt
213 w = pt.pt_warnings()
214 e = pt.pt_errors()
215 if e:
216 w = list(w) + list(e)
217 self.warnings = w