Mercurial > p > roundup > code
comparison roundup/cgi/PageTemplates/TALES.py @ 1049:b9988e118055
moved
| author | Richard Jones <richard@users.sourceforge.net> |
|---|---|
| date | Thu, 05 Sep 2002 00:37:09 +0000 |
| parents | |
| children | c08b3820edd1 |
comparison
equal
deleted
inserted
replaced
| 1048:1250251f2793 | 1049:b9988e118055 |
|---|---|
| 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 """TALES | |
| 14 | |
| 15 An implementation of a generic TALES engine | |
| 16 """ | |
| 17 | |
| 18 __version__='$Revision: 1.1 $'[11:-2] | |
| 19 | |
| 20 import re, sys | |
| 21 from roundup.cgi import ZTUtils | |
| 22 from MultiMapping import MultiMapping | |
| 23 | |
| 24 StringType = type('') | |
| 25 | |
| 26 NAME_RE = r"[a-zA-Z][a-zA-Z0-9_]*" | |
| 27 _parse_expr = re.compile(r"(%s):" % NAME_RE).match | |
| 28 _valid_name = re.compile('%s$' % NAME_RE).match | |
| 29 | |
| 30 class TALESError(Exception): | |
| 31 """Error during TALES expression evaluation""" | |
| 32 | |
| 33 class Undefined(TALESError): | |
| 34 '''Exception raised on traversal of an undefined path''' | |
| 35 | |
| 36 class RegistrationError(Exception): | |
| 37 '''TALES Type Registration Error''' | |
| 38 | |
| 39 class CompilerError(Exception): | |
| 40 '''TALES Compiler Error''' | |
| 41 | |
| 42 class Default: | |
| 43 '''Retain Default''' | |
| 44 Default = Default() | |
| 45 | |
| 46 _marker = [] | |
| 47 | |
| 48 class SafeMapping(MultiMapping): | |
| 49 '''Mapping with security declarations and limited method exposure. | |
| 50 | |
| 51 Since it subclasses MultiMapping, this class can be used to wrap | |
| 52 one or more mapping objects. Restricted Python code will not be | |
| 53 able to mutate the SafeMapping or the wrapped mappings, but will be | |
| 54 able to read any value. | |
| 55 ''' | |
| 56 __allow_access_to_unprotected_subobjects__ = 1 | |
| 57 push = pop = None | |
| 58 | |
| 59 _push = MultiMapping.push | |
| 60 _pop = MultiMapping.pop | |
| 61 | |
| 62 def has_get(self, key, _marker=[]): | |
| 63 v = self.get(key, _marker) | |
| 64 return v is not _marker, v | |
| 65 | |
| 66 class Iterator(ZTUtils.Iterator): | |
| 67 def __init__(self, name, seq, context): | |
| 68 ZTUtils.Iterator.__init__(self, seq) | |
| 69 self.name = name | |
| 70 self._context = context | |
| 71 | |
| 72 def next(self): | |
| 73 if ZTUtils.Iterator.next(self): | |
| 74 self._context.setLocal(self.name, self.item) | |
| 75 return 1 | |
| 76 return 0 | |
| 77 | |
| 78 | |
| 79 class ErrorInfo: | |
| 80 """Information about an exception passed to an on-error handler.""" | |
| 81 __allow_access_to_unprotected_subobjects__ = 1 | |
| 82 | |
| 83 def __init__(self, err, position=(None, None)): | |
| 84 if isinstance(err, Exception): | |
| 85 self.type = err.__class__ | |
| 86 self.value = err | |
| 87 else: | |
| 88 self.type = err | |
| 89 self.value = None | |
| 90 self.lineno = position[0] | |
| 91 self.offset = position[1] | |
| 92 | |
| 93 | |
| 94 class Engine: | |
| 95 '''Expression Engine | |
| 96 | |
| 97 An instance of this class keeps a mutable collection of expression | |
| 98 type handlers. It can compile expression strings by delegating to | |
| 99 these handlers. It can provide an expression Context, which is | |
| 100 capable of holding state and evaluating compiled expressions. | |
| 101 ''' | |
| 102 Iterator = Iterator | |
| 103 | |
| 104 def __init__(self, Iterator=None): | |
| 105 self.types = {} | |
| 106 if Iterator is not None: | |
| 107 self.Iterator = Iterator | |
| 108 | |
| 109 def registerType(self, name, handler): | |
| 110 if not _valid_name(name): | |
| 111 raise RegistrationError, 'Invalid Expression type "%s".' % name | |
| 112 types = self.types | |
| 113 if types.has_key(name): | |
| 114 raise RegistrationError, ( | |
| 115 'Multiple registrations for Expression type "%s".' % | |
| 116 name) | |
| 117 types[name] = handler | |
| 118 | |
| 119 def getTypes(self): | |
| 120 return self.types | |
| 121 | |
| 122 def compile(self, expression): | |
| 123 m = _parse_expr(expression) | |
| 124 if m: | |
| 125 type = m.group(1) | |
| 126 expr = expression[m.end():] | |
| 127 else: | |
| 128 type = "standard" | |
| 129 expr = expression | |
| 130 try: | |
| 131 handler = self.types[type] | |
| 132 except KeyError: | |
| 133 raise CompilerError, ( | |
| 134 'Unrecognized expression type "%s".' % type) | |
| 135 return handler(type, expr, self) | |
| 136 | |
| 137 def getContext(self, contexts=None, **kwcontexts): | |
| 138 if contexts is not None: | |
| 139 if kwcontexts: | |
| 140 kwcontexts.update(contexts) | |
| 141 else: | |
| 142 kwcontexts = contexts | |
| 143 return Context(self, kwcontexts) | |
| 144 | |
| 145 def getCompilerError(self): | |
| 146 return CompilerError | |
| 147 | |
| 148 class Context: | |
| 149 '''Expression Context | |
| 150 | |
| 151 An instance of this class holds context information that it can | |
| 152 use to evaluate compiled expressions. | |
| 153 ''' | |
| 154 | |
| 155 _context_class = SafeMapping | |
| 156 position = (None, None) | |
| 157 source_file = None | |
| 158 | |
| 159 def __init__(self, engine, contexts): | |
| 160 self._engine = engine | |
| 161 self.contexts = contexts | |
| 162 contexts['nothing'] = None | |
| 163 contexts['default'] = Default | |
| 164 | |
| 165 self.repeat_vars = rv = {} | |
| 166 # Wrap this, as it is visible to restricted code | |
| 167 contexts['repeat'] = rep = self._context_class(rv) | |
| 168 contexts['loop'] = rep # alias | |
| 169 | |
| 170 self.global_vars = gv = contexts.copy() | |
| 171 self.local_vars = lv = {} | |
| 172 self.vars = self._context_class(gv, lv) | |
| 173 | |
| 174 # Keep track of what needs to be popped as each scope ends. | |
| 175 self._scope_stack = [] | |
| 176 | |
| 177 def beginScope(self): | |
| 178 self._scope_stack.append([self.local_vars.copy()]) | |
| 179 | |
| 180 def endScope(self): | |
| 181 scope = self._scope_stack.pop() | |
| 182 self.local_vars = lv = scope[0] | |
| 183 v = self.vars | |
| 184 v._pop() | |
| 185 v._push(lv) | |
| 186 # Pop repeat variables, if any | |
| 187 i = len(scope) - 1 | |
| 188 while i: | |
| 189 name, value = scope[i] | |
| 190 if value is None: | |
| 191 del self.repeat_vars[name] | |
| 192 else: | |
| 193 self.repeat_vars[name] = value | |
| 194 i = i - 1 | |
| 195 | |
| 196 def setLocal(self, name, value): | |
| 197 self.local_vars[name] = value | |
| 198 | |
| 199 def setGlobal(self, name, value): | |
| 200 self.global_vars[name] = value | |
| 201 | |
| 202 def setRepeat(self, name, expr): | |
| 203 expr = self.evaluate(expr) | |
| 204 if not expr: | |
| 205 return self._engine.Iterator(name, (), self) | |
| 206 it = self._engine.Iterator(name, expr, self) | |
| 207 old_value = self.repeat_vars.get(name) | |
| 208 self._scope_stack[-1].append((name, old_value)) | |
| 209 self.repeat_vars[name] = it | |
| 210 return it | |
| 211 | |
| 212 def evaluate(self, expression, | |
| 213 isinstance=isinstance, StringType=StringType): | |
| 214 if isinstance(expression, StringType): | |
| 215 expression = self._engine.compile(expression) | |
| 216 __traceback_supplement__ = ( | |
| 217 TALESTracebackSupplement, self, expression) | |
| 218 v = expression(self) | |
| 219 return v | |
| 220 | |
| 221 evaluateValue = evaluate | |
| 222 | |
| 223 def evaluateBoolean(self, expr): | |
| 224 return not not self.evaluate(expr) | |
| 225 | |
| 226 def evaluateText(self, expr, None=None): | |
| 227 text = self.evaluate(expr) | |
| 228 if text is Default or text is None: | |
| 229 return text | |
| 230 return str(text) | |
| 231 | |
| 232 def evaluateStructure(self, expr): | |
| 233 return self.evaluate(expr) | |
| 234 evaluateStructure = evaluate | |
| 235 | |
| 236 def evaluateMacro(self, expr): | |
| 237 # XXX Should return None or a macro definition | |
| 238 return self.evaluate(expr) | |
| 239 evaluateMacro = evaluate | |
| 240 | |
| 241 def createErrorInfo(self, err, position): | |
| 242 return ErrorInfo(err, position) | |
| 243 | |
| 244 def getDefault(self): | |
| 245 return Default | |
| 246 | |
| 247 def setSourceFile(self, source_file): | |
| 248 self.source_file = source_file | |
| 249 | |
| 250 def setPosition(self, position): | |
| 251 self.position = position | |
| 252 | |
| 253 | |
| 254 | |
| 255 class TALESTracebackSupplement: | |
| 256 """Implementation of ITracebackSupplement""" | |
| 257 def __init__(self, context, expression): | |
| 258 self.context = context | |
| 259 self.source_url = context.source_file | |
| 260 self.line = context.position[0] | |
| 261 self.column = context.position[1] | |
| 262 self.expression = repr(expression) | |
| 263 | |
| 264 def getInfo(self, as_html=0): | |
| 265 import pprint | |
| 266 data = self.context.contexts.copy() | |
| 267 s = pprint.pformat(data) | |
| 268 if not as_html: | |
| 269 return ' - Names:\n %s' % string.replace(s, '\n', '\n ') | |
| 270 else: | |
| 271 from cgi import escape | |
| 272 return '<b>Names:</b><pre>%s</pre>' % (escape(s)) | |
| 273 return None | |
| 274 | |
| 275 | |
| 276 | |
| 277 class SimpleExpr: | |
| 278 '''Simple example of an expression type handler''' | |
| 279 def __init__(self, name, expr, engine): | |
| 280 self._name = name | |
| 281 self._expr = expr | |
| 282 def __call__(self, econtext): | |
| 283 return self._name, self._expr | |
| 284 def __repr__(self): | |
| 285 return '<SimpleExpr %s %s>' % (self._name, `self._expr`) | |
| 286 |
