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 roundup.cgi.TAL.TALParser import TALParser
23 from roundup.cgi.TAL.HTMLTALParser import HTMLTALParser
24 from roundup.cgi.TAL.TALGenerator import TALGenerator
25 from roundup.cgi.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 ComputedAttribute import ComputedAttribute
31 class PageTemplate:
32 "Page Templates using TAL, TALES, and METAL"
34 content_type = 'text/html'
35 expand = 0
36 _v_errors = ()
37 _v_warnings = ()
38 _v_program = None
39 _v_macros = None
40 _v_cooked = 0
41 id = '(unknown)'
42 _text = ''
43 _error_start = '<!-- Page Template Diagnostics'
45 def macros(self):
46 return self.pt_macros()
47 macros = ComputedAttribute(macros, 1)
49 def pt_edit(self, text, content_type):
50 if content_type:
51 self.content_type = str(content_type)
52 if hasattr(text, 'read'):
53 text = text.read()
54 self.write(text)
56 def pt_getContext(self):
57 c = {'template': self,
58 'options': {},
59 'nothing': None,
60 'request': None,
61 'modules': ModuleImporter,
62 }
63 parent = getattr(self, 'aq_parent', None)
64 if parent is not None:
65 c['here'] = parent
66 c['container'] = self.aq_inner.aq_parent
67 while parent is not None:
68 self = parent
69 parent = getattr(self, 'aq_parent', None)
70 c['root'] = self
71 return c
73 def pt_render(self, source=0, extra_context={}):
74 """Render this Page Template"""
75 if not self._v_cooked:
76 self._cook()
78 __traceback_supplement__ = (PageTemplateTracebackSupplement, self)
80 if self._v_errors:
81 raise PTRuntimeError, 'Page Template %s has errors.' % self.id
82 output = StringIO()
83 c = self.pt_getContext()
84 c.update(extra_context)
86 TALInterpreter(self._v_program, self._v_macros,
87 getEngine().getContext(c),
88 output,
89 tal=not source, strictinsert=0)()
90 return output.getvalue()
92 def __call__(self, *args, **kwargs):
93 if not kwargs.has_key('args'):
94 kwargs['args'] = args
95 return self.pt_render(extra_context={'options': kwargs})
97 def pt_errors(self):
98 if not self._v_cooked:
99 self._cook()
100 err = self._v_errors
101 if err:
102 return err
103 if not self.expand: return
104 try:
105 self.pt_render(source=1)
106 except:
107 return ('Macro expansion failed', '%s: %s' % sys.exc_info()[:2])
109 def pt_warnings(self):
110 if not self._v_cooked:
111 self._cook()
112 return self._v_warnings
114 def pt_macros(self):
115 if not self._v_cooked:
116 self._cook()
117 if self._v_errors:
118 __traceback_supplement__ = (PageTemplateTracebackSupplement, self)
119 raise PTRuntimeError, 'Page Template %s has errors.' % self.id
120 return self._v_macros
122 def pt_source_file(self):
123 return None # Unknown.
125 def write(self, text):
126 assert type(text) is type('')
127 if text[:len(self._error_start)] == self._error_start:
128 errend = find(text, '-->')
129 if errend >= 0:
130 text = text[errend + 4:]
131 if self._text != text:
132 self._text = text
133 self._cook()
135 def read(self):
136 if not self._v_cooked:
137 self._cook()
138 if not self._v_errors:
139 if not self.expand:
140 return self._text
141 try:
142 return self.pt_render(source=1)
143 except:
144 return ('%s\n Macro expansion failed\n %s\n-->\n%s' %
145 (self._error_start, "%s: %s" % sys.exc_info()[:2],
146 self._text) )
148 return ('%s\n %s\n-->\n%s' % (self._error_start,
149 join(self._v_errors, '\n '),
150 self._text))
152 def _cook(self):
153 """Compile the TAL and METAL statments.
155 Cooking must not fail due to compilation errors in templates.
156 """
157 source_file = self.pt_source_file()
158 if self.html():
159 gen = TALGenerator(getEngine(), xml=0, source_file=source_file)
160 parser = HTMLTALParser(gen)
161 else:
162 gen = TALGenerator(getEngine(), source_file=source_file)
163 parser = TALParser(gen)
165 self._v_errors = ()
166 try:
167 parser.parseString(self._text)
168 self._v_program, self._v_macros = parser.getCode()
169 except:
170 self._v_errors = ["Compilation failed",
171 "%s: %s" % sys.exc_info()[:2]]
172 self._v_warnings = parser.getWarnings()
173 self._v_cooked = 1
175 def html(self):
176 if not hasattr(getattr(self, 'aq_base', self), 'is_html'):
177 return self.content_type == 'text/html'
178 return self.is_html
180 class _ModuleImporter:
181 def __getitem__(self, module):
182 mod = __import__(module)
183 path = split(module, '.')
184 for name in path[1:]:
185 mod = getattr(mod, name)
186 return mod
188 ModuleImporter = _ModuleImporter()
190 class PTRuntimeError(RuntimeError):
191 '''The Page Template has template errors that prevent it from rendering.'''
192 pass
195 class PageTemplateTracebackSupplement:
196 #__implements__ = ITracebackSupplement
198 def __init__(self, pt):
199 self.object = pt
200 w = pt.pt_warnings()
201 e = pt.pt_errors()
202 if e:
203 w = list(w) + list(e)
204 self.warnings = w