author | Sylvain Thénault <sylvain.thenault@logilab.fr> |
Fri, 01 Jul 2011 18:44:03 +0200 | |
changeset 7612 | bfd4ee0d43ac |
parent 5424 | 8ecbcbff9777 |
child 8537 | e30d0a7f0087 |
permissions | -rw-r--r-- |
5421
8167de96c523
proper licensing information (LGPL-2.1). Hope I get it right this time.
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
4212
diff
changeset
|
1 |
# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
8167de96c523
proper licensing information (LGPL-2.1). Hope I get it right this time.
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
4212
diff
changeset
|
2 |
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr |
8167de96c523
proper licensing information (LGPL-2.1). Hope I get it right this time.
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
4212
diff
changeset
|
3 |
# |
8167de96c523
proper licensing information (LGPL-2.1). Hope I get it right this time.
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
4212
diff
changeset
|
4 |
# This file is part of CubicWeb. |
8167de96c523
proper licensing information (LGPL-2.1). Hope I get it right this time.
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
4212
diff
changeset
|
5 |
# |
8167de96c523
proper licensing information (LGPL-2.1). Hope I get it right this time.
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
4212
diff
changeset
|
6 |
# CubicWeb is free software: you can redistribute it and/or modify it under the |
8167de96c523
proper licensing information (LGPL-2.1). Hope I get it right this time.
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
4212
diff
changeset
|
7 |
# terms of the GNU Lesser General Public License as published by the Free |
8167de96c523
proper licensing information (LGPL-2.1). Hope I get it right this time.
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
4212
diff
changeset
|
8 |
# Software Foundation, either version 2.1 of the License, or (at your option) |
8167de96c523
proper licensing information (LGPL-2.1). Hope I get it right this time.
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
4212
diff
changeset
|
9 |
# any later version. |
8167de96c523
proper licensing information (LGPL-2.1). Hope I get it right this time.
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
4212
diff
changeset
|
10 |
# |
5424
8ecbcbff9777
replace logilab-common by CubicWeb in disclaimer
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
5421
diff
changeset
|
11 |
# CubicWeb is distributed in the hope that it will be useful, but WITHOUT |
5421
8167de96c523
proper licensing information (LGPL-2.1). Hope I get it right this time.
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
4212
diff
changeset
|
12 |
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
8167de96c523
proper licensing information (LGPL-2.1). Hope I get it right this time.
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
4212
diff
changeset
|
13 |
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more |
8167de96c523
proper licensing information (LGPL-2.1). Hope I get it right this time.
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
4212
diff
changeset
|
14 |
# details. |
8167de96c523
proper licensing information (LGPL-2.1). Hope I get it right this time.
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
4212
diff
changeset
|
15 |
# |
8167de96c523
proper licensing information (LGPL-2.1). Hope I get it right this time.
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
4212
diff
changeset
|
16 |
# You should have received a copy of the GNU Lesser General Public License along |
8167de96c523
proper licensing information (LGPL-2.1). Hope I get it right this time.
Sylvain Thénault <sylvain.thenault@logilab.fr>
parents:
4212
diff
changeset
|
17 |
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>. |
0 | 18 |
"""provides simpleTAL extensions for CubicWeb |
19 |
||
20 |
""" |
|
21 |
||
22 |
__docformat__ = "restructuredtext en" |
|
23 |
||
24 |
import sys |
|
25 |
import re |
|
26 |
from os.path import exists, isdir, join |
|
27 |
from logging import getLogger |
|
28 |
from StringIO import StringIO |
|
1802
d628defebc17
delete-trailing-whitespace + some copyright update
Adrien Di Mascio <Adrien.DiMascio@logilab.fr>
parents:
704
diff
changeset
|
29 |
|
0 | 30 |
from simpletal import simpleTAL, simpleTALES |
31 |
||
32 |
from logilab.common.decorators import cached |
|
33 |
||
34 |
LOGGER = getLogger('cubicweb.tal') |
|
35 |
||
36 |
||
37 |
class LoggerAdapter(object): |
|
38 |
def __init__(self, tal_logger): |
|
39 |
self.tal_logger = tal_logger |
|
1802
d628defebc17
delete-trailing-whitespace + some copyright update
Adrien Di Mascio <Adrien.DiMascio@logilab.fr>
parents:
704
diff
changeset
|
40 |
|
0 | 41 |
def debug(self, msg): |
42 |
LOGGER.debug(msg) |
|
43 |
||
44 |
def warn(self, msg): |
|
45 |
LOGGER.warning(msg) |
|
46 |
||
47 |
def __getattr__(self, attrname): |
|
48 |
return getattr(self.tal_logger, attrname) |
|
49 |
||
50 |
||
51 |
class CubicWebContext(simpleTALES.Context): |
|
52 |
"""add facilities to access entity / resultset""" |
|
53 |
||
54 |
def __init__(self, options=None, allowPythonPath=1): |
|
55 |
simpleTALES.Context.__init__(self, options, allowPythonPath) |
|
56 |
self.log = LoggerAdapter(self.log) |
|
57 |
||
58 |
def update(self, context): |
|
59 |
for varname, value in context.items(): |
|
60 |
self.addGlobal(varname, value) |
|
61 |
||
62 |
def addRepeat(self, name, var, initialValue): |
|
63 |
simpleTALES.Context.addRepeat(self, name, var, initialValue) |
|
64 |
||
65 |
# XXX FIXME need to find a clean to define OPCODE values for extensions |
|
1802
d628defebc17
delete-trailing-whitespace + some copyright update
Adrien Di Mascio <Adrien.DiMascio@logilab.fr>
parents:
704
diff
changeset
|
66 |
I18N_CONTENT = 18 |
0 | 67 |
I18N_REPLACE = 19 |
68 |
RQL_EXECUTE = 20 |
|
69 |
# simpleTAL uses the OPCODE values to define priority over commands. |
|
70 |
# TAL_ITER should have the same priority than TAL_REPEAT (i.e. 3), but |
|
71 |
# we can't use the same OPCODE for two different commands without changing |
|
72 |
# the simpleTAL implementation. Another solution would be to totally override |
|
73 |
# the REPEAT implementation with the ITER one, but some specific operations |
|
74 |
# (involving len() for instance) are not implemented for ITER, so we prefer |
|
75 |
# to keep both implementations for now, and to fool simpleTAL by using a float |
|
76 |
# number between 3 and 4 |
|
77 |
TAL_ITER = 3.1 |
|
78 |
||
79 |
||
80 |
# FIX simpleTAL HTML 4.01 stupidity |
|
81 |
# (simpleTAL never closes tags like INPUT, IMG, HR ...) |
|
82 |
simpleTAL.HTML_FORBIDDEN_ENDTAG.clear() |
|
83 |
||
84 |
class CubicWebTemplateCompiler(simpleTAL.HTMLTemplateCompiler): |
|
85 |
"""extends default compiler by adding i18n:content commands""" |
|
86 |
||
87 |
def __init__(self): |
|
88 |
simpleTAL.HTMLTemplateCompiler.__init__(self) |
|
89 |
self.commandHandler[I18N_CONTENT] = self.compile_cmd_i18n_content |
|
90 |
self.commandHandler[I18N_REPLACE] = self.compile_cmd_i18n_replace |
|
91 |
self.commandHandler[RQL_EXECUTE] = self.compile_cmd_rql |
|
92 |
self.commandHandler[TAL_ITER] = self.compile_cmd_tal_iter |
|
93 |
||
94 |
def setTALPrefix(self, prefix): |
|
95 |
simpleTAL.TemplateCompiler.setTALPrefix(self, prefix) |
|
96 |
self.tal_attribute_map['i18n:content'] = I18N_CONTENT |
|
97 |
self.tal_attribute_map['i18n:replace'] = I18N_REPLACE |
|
98 |
self.tal_attribute_map['rql:execute'] = RQL_EXECUTE |
|
99 |
self.tal_attribute_map['tal:iter'] = TAL_ITER |
|
100 |
||
101 |
def compile_cmd_i18n_content(self, argument): |
|
102 |
# XXX tal:content structure=, text= should we support this ? |
|
103 |
structure_flag = 0 |
|
104 |
return (I18N_CONTENT, (argument, False, structure_flag, self.endTagSymbol)) |
|
105 |
||
106 |
def compile_cmd_i18n_replace(self, argument): |
|
107 |
# XXX tal:content structure=, text= should we support this ? |
|
108 |
structure_flag = 0 |
|
109 |
return (I18N_CONTENT, (argument, True, structure_flag, self.endTagSymbol)) |
|
110 |
||
111 |
def compile_cmd_rql(self, argument): |
|
112 |
return (RQL_EXECUTE, (argument, self.endTagSymbol)) |
|
113 |
||
114 |
def compile_cmd_tal_iter(self, argument): |
|
115 |
original_id, (var_name, expression, end_tag_symbol) = \ |
|
116 |
simpleTAL.HTMLTemplateCompiler.compileCmdRepeat(self, argument) |
|
117 |
return (TAL_ITER, (var_name, expression, self.endTagSymbol)) |
|
118 |
||
119 |
def getTemplate(self): |
|
120 |
return CubicWebTemplate(self.commandList, self.macroMap, self.symbolLocationTable) |
|
121 |
||
122 |
def compileCmdAttributes (self, argument): |
|
123 |
"""XXX modified to support single attribute |
|
124 |
definition ending by a ';' |
|
125 |
||
126 |
backport this to simpleTAL |
|
127 |
""" |
|
128 |
# Compile tal:attributes into attribute command |
|
129 |
# Argument: [(attributeName, expression)] |
|
1802
d628defebc17
delete-trailing-whitespace + some copyright update
Adrien Di Mascio <Adrien.DiMascio@logilab.fr>
parents:
704
diff
changeset
|
130 |
|
0 | 131 |
# Break up the list of attribute settings first |
132 |
commandArgs = [] |
|
133 |
# We only want to match semi-colons that are not escaped |
|
134 |
argumentSplitter = re.compile(r'(?<!;);(?!;)') |
|
135 |
for attributeStmt in argumentSplitter.split(argument): |
|
136 |
if not attributeStmt.strip(): |
|
137 |
continue |
|
138 |
# remove any leading space and un-escape any semi-colons |
|
139 |
attributeStmt = attributeStmt.lstrip().replace(';;', ';') |
|
140 |
# Break each attributeStmt into name and expression |
|
141 |
stmtBits = attributeStmt.split(' ') |
|
142 |
if (len (stmtBits) < 2): |
|
143 |
# Error, badly formed attributes command |
|
144 |
msg = "Badly formed attributes command '%s'. Attributes commands must be of the form: 'name expression[;name expression]'" % argument |
|
145 |
self.log.error(msg) |
|
146 |
raise simpleTAL.TemplateParseException(self.tagAsText(self.currentStartTag), msg) |
|
147 |
attName = stmtBits[0] |
|
148 |
attExpr = " ".join(stmtBits[1:]) |
|
149 |
commandArgs.append((attName, attExpr)) |
|
150 |
return (simpleTAL.TAL_ATTRIBUTES, commandArgs) |
|
151 |
||
152 |
||
153 |
class CubicWebTemplateInterpreter(simpleTAL.TemplateInterpreter): |
|
154 |
"""provides implementation for interpreting cubicweb extensions""" |
|
155 |
def __init__(self): |
|
156 |
simpleTAL.TemplateInterpreter.__init__(self) |
|
157 |
self.commandHandler[I18N_CONTENT] = self.cmd_i18n |
|
158 |
self.commandHandler[TAL_ITER] = self.cmdRepeat |
|
159 |
# self.commandHandler[RQL_EXECUTE] = self.cmd_rql |
|
160 |
||
161 |
def cmd_i18n(self, command, args): |
|
162 |
"""i18n:content and i18n:replace implementation""" |
|
163 |
string, replace_flag, structure_flag, end_symbol = args |
|
164 |
if replace_flag: |
|
165 |
self.outputTag = 0 |
|
166 |
result = self.context.globals['_'](string) |
|
167 |
self.tagContent = (0, result) |
|
168 |
self.movePCForward = self.symbolTable[end_symbol] |
|
169 |
self.programCounter += 1 |
|
170 |
||
171 |
||
172 |
class CubicWebTemplate(simpleTAL.HTMLTemplate): |
|
173 |
"""overrides HTMLTemplate.expand() to systematically use CubicWebInterpreter |
|
174 |
""" |
|
175 |
def expand(self, context, outputFile): |
|
176 |
interpreter = CubicWebTemplateInterpreter() |
|
177 |
interpreter.initialise(context, outputFile) |
|
178 |
simpleTAL.HTMLTemplate.expand(self, context, outputFile,# outputEncoding='unicode', |
|
179 |
interpreter=interpreter) |
|
180 |
||
181 |
def expandInline(self, context, outputFile, interpreter): |
|
182 |
""" Internally used when expanding a template that is part of a context.""" |
|
183 |
try: |
|
184 |
interpreter.execute(self) |
|
185 |
except UnicodeError, unierror: |
|
186 |
LOGGER.exception(str(unierror)) |
|
187 |
raise simpleTALES.ContextContentException("found non-unicode %r string in Context!" % unierror.args[1]), None, sys.exc_info()[-1] |
|
188 |
||
189 |
||
190 |
def compile_template(template): |
|
191 |
"""compiles a TAL template string |
|
192 |
:type template: unicode |
|
193 |
:param template: a TAL-compliant template string |
|
194 |
""" |
|
195 |
string_buffer = StringIO(template) |
|
196 |
compiler = CubicWebTemplateCompiler() |
|
197 |
compiler.parseTemplate(string_buffer) # , inputEncoding='unicode') |
|
198 |
return compiler.getTemplate() |
|
199 |
||
200 |
||
201 |
def compile_template_file(filepath): |
|
202 |
"""compiles a TAL template file |
|
203 |
:type filepath: str |
|
1802
d628defebc17
delete-trailing-whitespace + some copyright update
Adrien Di Mascio <Adrien.DiMascio@logilab.fr>
parents:
704
diff
changeset
|
204 |
:param template: path of the file to compile |
0 | 205 |
""" |
206 |
fp = file(filepath) |
|
207 |
file_content = unicode(fp.read()) # template file should be pure ASCII |
|
208 |
fp.close() |
|
209 |
return compile_template(file_content) |
|
210 |
||
211 |
||
212 |
def evaluatePython (self, expr): |
|
213 |
if not self.allowPythonPath: |
|
214 |
return self.false |
|
215 |
globals = {} |
|
216 |
for name, value in self.globals.items(): |
|
217 |
if isinstance (value, simpleTALES.ContextVariable): |
|
218 |
value = value.rawValue() |
|
219 |
globals[name] = value |
|
220 |
globals['path'] = self.pythonPathFuncs.path |
|
221 |
globals['string'] = self.pythonPathFuncs.string |
|
222 |
globals['exists'] = self.pythonPathFuncs.exists |
|
223 |
globals['nocall'] = self.pythonPathFuncs.nocall |
|
224 |
globals['test'] = self.pythonPathFuncs.test |
|
225 |
locals = {} |
|
226 |
for name, value in self.locals.items(): |
|
227 |
if (isinstance (value, simpleTALES.ContextVariable)): |
|
228 |
value = value.rawValue() |
|
229 |
locals[name] = value |
|
230 |
# XXX precompile expr will avoid late syntax error |
|
231 |
try: |
|
232 |
result = eval(expr, globals, locals) |
|
233 |
except Exception, ex: |
|
234 |
ex = ex.__class__('in %r: %s' % (expr, ex)) |
|
235 |
raise ex, None, sys.exc_info()[-1] |
|
236 |
if (isinstance (result, simpleTALES.ContextVariable)): |
|
237 |
return result.value() |
|
238 |
return result |
|
239 |
||
240 |
simpleTALES.Context.evaluatePython = evaluatePython |
|
241 |
||
242 |
||
243 |
class talbased(object): |
|
244 |
def __init__(self, filename, write=True): |
|
245 |
## if not osp.isfile(filepath): |
|
246 |
## # print "[tal.py] just for tests..." |
|
247 |
## # get parent frame |
|
248 |
## directory = osp.abspath(osp.dirname(sys._getframe(1).f_globals['__file__'])) |
|
249 |
## filepath = osp.join(directory, filepath) |
|
250 |
self.filename = filename |
|
251 |
self.write = write |
|
252 |
||
253 |
def __call__(self, viewfunc): |
|
254 |
def wrapped(instance, *args, **kwargs): |
|
255 |
variables = viewfunc(instance, *args, **kwargs) |
|
256 |
html = instance.tal_render(self._compiled_template(instance), variables) |
|
257 |
if self.write: |
|
258 |
instance.w(html) |
|
259 |
else: |
|
260 |
return html |
|
261 |
return wrapped |
|
262 |
||
263 |
def _compiled_template(self, instance): |
|
264 |
for fileordirectory in instance.config.vregistry_path(): |
|
265 |
filepath = join(fileordirectory, self.filename) |
|
266 |
if isdir(fileordirectory) and exists(filepath): |
|
267 |
return compile_template_file(filepath) |
|
268 |
raise Exception('no such template %s' % self.filename) |
|
269 |
_compiled_template = cached(_compiled_template, 0) |