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 """
23 __docformat__ = 'restructuredtext'
25 __version__='$Revision: 1.4 $'[11:-2]
27 import sys
29 from roundup.cgi.TAL.TALParser import TALParser
30 from roundup.cgi.TAL.HTMLTALParser import HTMLTALParser
31 from roundup.cgi.TAL.TALGenerator import TALGenerator
32 from roundup.cgi.TAL.TALInterpreter import TALInterpreter
33 from Expressions import getEngine
34 from string import join, strip, rstrip, split, replace, lower, find
35 from cStringIO import StringIO
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 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 __traceback_supplement__ = (PageTemplateTracebackSupplement, self)
120 if self._v_errors:
121 raise PTRuntimeError, 'Page Template %s has errors.' % self.id
122 return self._v_macros
124 def __getattr__(self, name):
125 if name == 'macros':
126 return self.pt_macros()
127 raise AttributeError, name
129 def pt_source_file(self):
130 return None # Unknown.
132 def write(self, text):
133 assert type(text) is type('')
134 if text[:len(self._error_start)] == self._error_start:
135 errend = find(text, '-->')
136 if errend >= 0:
137 text = text[errend + 4:]
138 if self._text != text:
139 self._text = text
140 self._cook()
142 def read(self):
143 if not self._v_cooked:
144 self._cook()
145 if not self._v_errors:
146 if not self.expand:
147 return self._text
148 try:
149 return self.pt_render(source=1)
150 except:
151 return ('%s\n Macro expansion failed\n %s\n-->\n%s' %
152 (self._error_start, "%s: %s" % sys.exc_info()[:2],
153 self._text) )
155 return ('%s\n %s\n-->\n%s' % (self._error_start,
156 join(self._v_errors, '\n '),
157 self._text))
159 def _cook(self):
160 """Compile the TAL and METAL statments.
162 Cooking must not fail due to compilation errors in templates.
163 """
164 source_file = self.pt_source_file()
165 if self.html():
166 gen = TALGenerator(getEngine(), xml=0, source_file=source_file)
167 parser = HTMLTALParser(gen)
168 else:
169 gen = TALGenerator(getEngine(), source_file=source_file)
170 parser = TALParser(gen)
172 self._v_errors = ()
173 try:
174 parser.parseString(self._text)
175 self._v_program, self._v_macros = parser.getCode()
176 except:
177 self._v_errors = ["Compilation failed",
178 "%s: %s" % sys.exc_info()[:2]]
179 self._v_warnings = parser.getWarnings()
180 self._v_cooked = 1
182 def html(self):
183 if not hasattr(getattr(self, 'aq_base', self), 'is_html'):
184 return self.content_type == 'text/html'
185 return self.is_html
187 class _ModuleImporter:
188 def __getitem__(self, module):
189 mod = __import__(module)
190 path = split(module, '.')
191 for name in path[1:]:
192 mod = getattr(mod, name)
193 return mod
195 ModuleImporter = _ModuleImporter()
197 class PTRuntimeError(RuntimeError):
198 '''The Page Template has template errors that prevent it from rendering.'''
199 pass
202 class PageTemplateTracebackSupplement:
203 #__implements__ = ITracebackSupplement
205 def __init__(self, pt):
206 self.object = pt
207 w = pt.pt_warnings()
208 e = pt.pt_errors()
209 if e:
210 w = list(w) + list(e)
211 self.warnings = w