| 1 | # -*- coding: utf-8 -*- |
|---|
| 2 | # |
|---|
| 3 | # Copyright (C) 2006-2007 Edgewall Software |
|---|
| 4 | # All rights reserved. |
|---|
| 5 | # |
|---|
| 6 | # This software is licensed as described in the file COPYING, which |
|---|
| 7 | # you should have received as part of this distribution. The terms |
|---|
| 8 | # are also available at http://genshi.edgewall.org/wiki/License. |
|---|
| 9 | # |
|---|
| 10 | # This software consists of voluntary contributions made by many |
|---|
| 11 | # individuals. For the exact contribution history, see the revision |
|---|
| 12 | # history and logs, available at http://genshi.edgewall.org/log/. |
|---|
| 13 | |
|---|
| 14 | """Implementation of the various template directives.""" |
|---|
| 15 | |
|---|
| 16 | import compiler |
|---|
| 17 | |
|---|
| 18 | from genshi.core import Attrs, QName, Stream |
|---|
| 19 | from genshi.path import Path |
|---|
| 20 | from genshi.template.base import TemplateRuntimeError, TemplateSyntaxError, \ |
|---|
| 21 | EXPR, _apply_directives |
|---|
| 22 | from genshi.template.eval import Expression, _parse |
|---|
| 23 | |
|---|
| 24 | __all__ = ['AttrsDirective', 'ChooseDirective', 'ContentDirective', |
|---|
| 25 | 'DefDirective', 'ForDirective', 'IfDirective', 'MatchDirective', |
|---|
| 26 | 'OtherwiseDirective', 'ReplaceDirective', 'StripDirective', |
|---|
| 27 | 'WhenDirective', 'WithDirective'] |
|---|
| 28 | __docformat__ = 'restructuredtext en' |
|---|
| 29 | |
|---|
| 30 | |
|---|
| 31 | class DirectiveMeta(type): |
|---|
| 32 | """Meta class for template directives.""" |
|---|
| 33 | |
|---|
| 34 | def __new__(cls, name, bases, d): |
|---|
| 35 | d['tagname'] = name.lower().replace('directive', '') |
|---|
| 36 | return type.__new__(cls, name, bases, d) |
|---|
| 37 | |
|---|
| 38 | |
|---|
| 39 | class Directive(object): |
|---|
| 40 | """Abstract base class for template directives. |
|---|
| 41 | |
|---|
| 42 | A directive is basically a callable that takes three positional arguments: |
|---|
| 43 | ``ctxt`` is the template data context, ``stream`` is an iterable over the |
|---|
| 44 | events that the directive applies to, and ``directives`` is is a list of |
|---|
| 45 | other directives on the same stream that need to be applied. |
|---|
| 46 | |
|---|
| 47 | Directives can be "anonymous" or "registered". Registered directives can be |
|---|
| 48 | applied by the template author using an XML attribute with the |
|---|
| 49 | corresponding name in the template. Such directives should be subclasses of |
|---|
| 50 | this base class that can be instantiated with the value of the directive |
|---|
| 51 | attribute as parameter. |
|---|
| 52 | |
|---|
| 53 | Anonymous directives are simply functions conforming to the protocol |
|---|
| 54 | described above, and can only be applied programmatically (for example by |
|---|
| 55 | template filters). |
|---|
| 56 | """ |
|---|
| 57 | __metaclass__ = DirectiveMeta |
|---|
| 58 | __slots__ = ['expr'] |
|---|
| 59 | |
|---|
| 60 | def __init__(self, value, template=None, namespaces=None, lineno=-1, |
|---|
| 61 | offset=-1): |
|---|
| 62 | self.expr = self._parse_expr(value, template, lineno, offset) |
|---|
| 63 | |
|---|
| 64 | def attach(cls, template, stream, value, namespaces, pos): |
|---|
| 65 | """Called after the template stream has been completely parsed. |
|---|
| 66 | |
|---|
| 67 | :param template: the `Template` object |
|---|
| 68 | :param stream: the event stream associated with the directive |
|---|
| 69 | :param value: the argument value for the directive |
|---|
| 70 | :param namespaces: a mapping of namespace URIs to prefixes |
|---|
| 71 | :param pos: a ``(filename, lineno, offset)`` tuple describing the |
|---|
| 72 | location where the directive was found in the source |
|---|
| 73 | |
|---|
| 74 | This class method should return a ``(directive, stream)`` tuple. If |
|---|
| 75 | ``directive`` is not ``None``, it should be an instance of the `Directive` |
|---|
| 76 | class, and gets added to the list of directives applied to the substream |
|---|
| 77 | at runtime. `stream` is an event stream that replaces the original |
|---|
| 78 | stream associated with the directive. |
|---|
| 79 | """ |
|---|
| 80 | return cls(value, template, namespaces, *pos[1:]), stream |
|---|
| 81 | attach = classmethod(attach) |
|---|
| 82 | |
|---|
| 83 | def __call__(self, stream, ctxt, directives): |
|---|
| 84 | """Apply the directive to the given stream. |
|---|
| 85 | |
|---|
| 86 | :param stream: the event stream |
|---|
| 87 | :param ctxt: the context data |
|---|
| 88 | :param directives: a list of the remaining directives that should |
|---|
| 89 | process the stream |
|---|
| 90 | """ |
|---|
| 91 | raise NotImplementedError |
|---|
| 92 | |
|---|
| 93 | def __repr__(self): |
|---|
| 94 | expr = '' |
|---|
| 95 | if getattr(self, 'expr', None) is not None: |
|---|
| 96 | expr = ' "%s"' % self.expr.source |
|---|
| 97 | return '<%s%s>' % (self.__class__.__name__, expr) |
|---|
| 98 | |
|---|
| 99 | def _parse_expr(cls, expr, template, lineno=-1, offset=-1): |
|---|
| 100 | """Parses the given expression, raising a useful error message when a |
|---|
| 101 | syntax error is encountered. |
|---|
| 102 | """ |
|---|
| 103 | try: |
|---|
| 104 | return expr and Expression(expr, template.filepath, lineno, |
|---|
| 105 | lookup=template.lookup) or None |
|---|
| 106 | except SyntaxError, err: |
|---|
| 107 | err.msg += ' in expression "%s" of "%s" directive' % (expr, |
|---|
| 108 | cls.tagname) |
|---|
| 109 | raise TemplateSyntaxError(err, template.filepath, lineno, |
|---|
| 110 | offset + (err.offset or 0)) |
|---|
| 111 | _parse_expr = classmethod(_parse_expr) |
|---|
| 112 | |
|---|
| 113 | |
|---|
| 114 | def _assignment(ast): |
|---|
| 115 | """Takes the AST representation of an assignment, and returns a function |
|---|
| 116 | that applies the assignment of a given value to a dictionary. |
|---|
| 117 | """ |
|---|
| 118 | def _names(node): |
|---|
| 119 | if isinstance(node, (compiler.ast.AssTuple, compiler.ast.Tuple)): |
|---|
| 120 | return tuple([_names(child) for child in node.nodes]) |
|---|
| 121 | elif isinstance(node, (compiler.ast.AssName, compiler.ast.Name)): |
|---|
| 122 | return node.name |
|---|
| 123 | def _assign(data, value, names=_names(ast)): |
|---|
| 124 | if type(names) is tuple: |
|---|
| 125 | for idx in range(len(names)): |
|---|
| 126 | _assign(data, value[idx], names[idx]) |
|---|
| 127 | else: |
|---|
| 128 | data[names] = value |
|---|
| 129 | return _assign |
|---|
| 130 | |
|---|
| 131 | |
|---|
| 132 | class AttrsDirective(Directive): |
|---|
| 133 | """Implementation of the ``py:attrs`` template directive. |
|---|
| 134 | |
|---|
| 135 | The value of the ``py:attrs`` attribute should be a dictionary or a sequence |
|---|
| 136 | of ``(name, value)`` tuples. The items in that dictionary or sequence are |
|---|
| 137 | added as attributes to the element: |
|---|
| 138 | |
|---|
| 139 | >>> from genshi.template import MarkupTemplate |
|---|
| 140 | >>> tmpl = MarkupTemplate('''<ul xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 141 | ... <li py:attrs="foo">Bar</li> |
|---|
| 142 | ... </ul>''') |
|---|
| 143 | >>> print tmpl.generate(foo={'class': 'collapse'}) |
|---|
| 144 | <ul> |
|---|
| 145 | <li class="collapse">Bar</li> |
|---|
| 146 | </ul> |
|---|
| 147 | >>> print tmpl.generate(foo=[('class', 'collapse')]) |
|---|
| 148 | <ul> |
|---|
| 149 | <li class="collapse">Bar</li> |
|---|
| 150 | </ul> |
|---|
| 151 | |
|---|
| 152 | If the value evaluates to ``None`` (or any other non-truth value), no |
|---|
| 153 | attributes are added: |
|---|
| 154 | |
|---|
| 155 | >>> print tmpl.generate(foo=None) |
|---|
| 156 | <ul> |
|---|
| 157 | <li>Bar</li> |
|---|
| 158 | </ul> |
|---|
| 159 | """ |
|---|
| 160 | __slots__ = [] |
|---|
| 161 | |
|---|
| 162 | def __call__(self, stream, ctxt, directives): |
|---|
| 163 | def _generate(): |
|---|
| 164 | kind, (tag, attrib), pos = stream.next() |
|---|
| 165 | attrs = self.expr.evaluate(ctxt) |
|---|
| 166 | if attrs: |
|---|
| 167 | if isinstance(attrs, Stream): |
|---|
| 168 | try: |
|---|
| 169 | attrs = iter(attrs).next() |
|---|
| 170 | except StopIteration: |
|---|
| 171 | attrs = [] |
|---|
| 172 | elif not isinstance(attrs, list): # assume it's a dict |
|---|
| 173 | attrs = attrs.items() |
|---|
| 174 | attrib -= [name for name, val in attrs if val is None] |
|---|
| 175 | attrib |= [(QName(name), unicode(val).strip()) for name, val |
|---|
| 176 | in attrs if val is not None] |
|---|
| 177 | yield kind, (tag, attrib), pos |
|---|
| 178 | for event in stream: |
|---|
| 179 | yield event |
|---|
| 180 | |
|---|
| 181 | return _apply_directives(_generate(), ctxt, directives) |
|---|
| 182 | |
|---|
| 183 | |
|---|
| 184 | class ContentDirective(Directive): |
|---|
| 185 | """Implementation of the ``py:content`` template directive. |
|---|
| 186 | |
|---|
| 187 | This directive replaces the content of the element with the result of |
|---|
| 188 | evaluating the value of the ``py:content`` attribute: |
|---|
| 189 | |
|---|
| 190 | >>> from genshi.template import MarkupTemplate |
|---|
| 191 | >>> tmpl = MarkupTemplate('''<ul xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 192 | ... <li py:content="bar">Hello</li> |
|---|
| 193 | ... </ul>''') |
|---|
| 194 | >>> print tmpl.generate(bar='Bye') |
|---|
| 195 | <ul> |
|---|
| 196 | <li>Bye</li> |
|---|
| 197 | </ul> |
|---|
| 198 | """ |
|---|
| 199 | __slots__ = [] |
|---|
| 200 | |
|---|
| 201 | def attach(cls, template, stream, value, namespaces, pos): |
|---|
| 202 | expr = cls._parse_expr(value, template, *pos[1:]) |
|---|
| 203 | return None, [stream[0], (EXPR, expr, pos), stream[-1]] |
|---|
| 204 | attach = classmethod(attach) |
|---|
| 205 | |
|---|
| 206 | |
|---|
| 207 | class DefDirective(Directive): |
|---|
| 208 | """Implementation of the ``py:def`` template directive. |
|---|
| 209 | |
|---|
| 210 | This directive can be used to create "Named Template Functions", which |
|---|
| 211 | are template snippets that are not actually output during normal |
|---|
| 212 | processing, but rather can be expanded from expressions in other places |
|---|
| 213 | in the template. |
|---|
| 214 | |
|---|
| 215 | A named template function can be used just like a normal Python function |
|---|
| 216 | from template expressions: |
|---|
| 217 | |
|---|
| 218 | >>> from genshi.template import MarkupTemplate |
|---|
| 219 | >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 220 | ... <p py:def="echo(greeting, name='world')" class="message"> |
|---|
| 221 | ... ${greeting}, ${name}! |
|---|
| 222 | ... </p> |
|---|
| 223 | ... ${echo('Hi', name='you')} |
|---|
| 224 | ... </div>''') |
|---|
| 225 | >>> print tmpl.generate(bar='Bye') |
|---|
| 226 | <div> |
|---|
| 227 | <p class="message"> |
|---|
| 228 | Hi, you! |
|---|
| 229 | </p> |
|---|
| 230 | </div> |
|---|
| 231 | |
|---|
| 232 | If a function does not require parameters, the parenthesis can be omitted |
|---|
| 233 | in the definition: |
|---|
| 234 | |
|---|
| 235 | >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 236 | ... <p py:def="helloworld" class="message"> |
|---|
| 237 | ... Hello, world! |
|---|
| 238 | ... </p> |
|---|
| 239 | ... ${helloworld()} |
|---|
| 240 | ... </div>''') |
|---|
| 241 | >>> print tmpl.generate(bar='Bye') |
|---|
| 242 | <div> |
|---|
| 243 | <p class="message"> |
|---|
| 244 | Hello, world! |
|---|
| 245 | </p> |
|---|
| 246 | </div> |
|---|
| 247 | """ |
|---|
| 248 | __slots__ = ['name', 'args', 'star_args', 'dstar_args', 'defaults'] |
|---|
| 249 | |
|---|
| 250 | ATTRIBUTE = 'function' |
|---|
| 251 | |
|---|
| 252 | def __init__(self, args, template, namespaces=None, lineno=-1, offset=-1): |
|---|
| 253 | Directive.__init__(self, None, template, namespaces, lineno, offset) |
|---|
| 254 | ast = _parse(args).node |
|---|
| 255 | self.args = [] |
|---|
| 256 | self.star_args = None |
|---|
| 257 | self.dstar_args = None |
|---|
| 258 | self.defaults = {} |
|---|
| 259 | if isinstance(ast, compiler.ast.CallFunc): |
|---|
| 260 | self.name = ast.node.name |
|---|
| 261 | for arg in ast.args: |
|---|
| 262 | if isinstance(arg, compiler.ast.Keyword): |
|---|
| 263 | self.args.append(arg.name) |
|---|
| 264 | self.defaults[arg.name] = Expression(arg.expr, |
|---|
| 265 | template.filepath, |
|---|
| 266 | lineno, |
|---|
| 267 | lookup=template.lookup) |
|---|
| 268 | else: |
|---|
| 269 | self.args.append(arg.name) |
|---|
| 270 | if ast.star_args: |
|---|
| 271 | self.star_args = ast.star_args.name |
|---|
| 272 | if ast.dstar_args: |
|---|
| 273 | self.dstar_args = ast.dstar_args.name |
|---|
| 274 | else: |
|---|
| 275 | self.name = ast.name |
|---|
| 276 | |
|---|
| 277 | def __call__(self, stream, ctxt, directives): |
|---|
| 278 | stream = list(stream) |
|---|
| 279 | |
|---|
| 280 | def function(*args, **kwargs): |
|---|
| 281 | scope = {} |
|---|
| 282 | args = list(args) # make mutable |
|---|
| 283 | for name in self.args: |
|---|
| 284 | if args: |
|---|
| 285 | scope[name] = args.pop(0) |
|---|
| 286 | else: |
|---|
| 287 | if name in kwargs: |
|---|
| 288 | val = kwargs.pop(name) |
|---|
| 289 | else: |
|---|
| 290 | val = self.defaults.get(name).evaluate(ctxt) |
|---|
| 291 | scope[name] = val |
|---|
| 292 | if not self.star_args is None: |
|---|
| 293 | scope[self.star_args] = args |
|---|
| 294 | if not self.dstar_args is None: |
|---|
| 295 | scope[self.dstar_args] = kwargs |
|---|
| 296 | ctxt.push(scope) |
|---|
| 297 | for event in _apply_directives(stream, ctxt, directives): |
|---|
| 298 | yield event |
|---|
| 299 | ctxt.pop() |
|---|
| 300 | try: |
|---|
| 301 | function.__name__ = self.name |
|---|
| 302 | except TypeError: |
|---|
| 303 | # Function name can't be set in Python 2.3 |
|---|
| 304 | pass |
|---|
| 305 | |
|---|
| 306 | # Store the function reference in the bottom context frame so that it |
|---|
| 307 | # doesn't get popped off before processing the template has finished |
|---|
| 308 | # FIXME: this makes context data mutable as a side-effect |
|---|
| 309 | ctxt.frames[-1][self.name] = function |
|---|
| 310 | |
|---|
| 311 | return [] |
|---|
| 312 | |
|---|
| 313 | def __repr__(self): |
|---|
| 314 | return '<%s "%s">' % (self.__class__.__name__, self.name) |
|---|
| 315 | |
|---|
| 316 | |
|---|
| 317 | class ForDirective(Directive): |
|---|
| 318 | """Implementation of the ``py:for`` template directive for repeating an |
|---|
| 319 | element based on an iterable in the context data. |
|---|
| 320 | |
|---|
| 321 | >>> from genshi.template import MarkupTemplate |
|---|
| 322 | >>> tmpl = MarkupTemplate('''<ul xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 323 | ... <li py:for="item in items">${item}</li> |
|---|
| 324 | ... </ul>''') |
|---|
| 325 | >>> print tmpl.generate(items=[1, 2, 3]) |
|---|
| 326 | <ul> |
|---|
| 327 | <li>1</li><li>2</li><li>3</li> |
|---|
| 328 | </ul> |
|---|
| 329 | """ |
|---|
| 330 | __slots__ = ['assign', 'filename'] |
|---|
| 331 | |
|---|
| 332 | ATTRIBUTE = 'each' |
|---|
| 333 | |
|---|
| 334 | def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1): |
|---|
| 335 | if ' in ' not in value: |
|---|
| 336 | raise TemplateSyntaxError('"in" keyword missing in "for" directive', |
|---|
| 337 | template.filepath, lineno, offset) |
|---|
| 338 | assign, value = value.split(' in ', 1) |
|---|
| 339 | ast = _parse(assign, 'exec') |
|---|
| 340 | self.assign = _assignment(ast.node.nodes[0].expr) |
|---|
| 341 | self.filename = template.filepath |
|---|
| 342 | Directive.__init__(self, value.strip(), template, namespaces, lineno, |
|---|
| 343 | offset) |
|---|
| 344 | |
|---|
| 345 | def __call__(self, stream, ctxt, directives): |
|---|
| 346 | iterable = self.expr.evaluate(ctxt) |
|---|
| 347 | if iterable is None: |
|---|
| 348 | return |
|---|
| 349 | |
|---|
| 350 | assign = self.assign |
|---|
| 351 | scope = {} |
|---|
| 352 | stream = list(stream) |
|---|
| 353 | try: |
|---|
| 354 | iterator = iter(iterable) |
|---|
| 355 | for item in iterator: |
|---|
| 356 | assign(scope, item) |
|---|
| 357 | ctxt.push(scope) |
|---|
| 358 | for event in _apply_directives(stream, ctxt, directives): |
|---|
| 359 | yield event |
|---|
| 360 | ctxt.pop() |
|---|
| 361 | except TypeError, e: |
|---|
| 362 | raise TemplateRuntimeError(str(e), self.filename, *stream[0][2][1:]) |
|---|
| 363 | |
|---|
| 364 | def __repr__(self): |
|---|
| 365 | return '<%s>' % self.__class__.__name__ |
|---|
| 366 | |
|---|
| 367 | |
|---|
| 368 | class IfDirective(Directive): |
|---|
| 369 | """Implementation of the ``py:if`` template directive for conditionally |
|---|
| 370 | excluding elements from being output. |
|---|
| 371 | |
|---|
| 372 | >>> from genshi.template import MarkupTemplate |
|---|
| 373 | >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 374 | ... <b py:if="foo">${bar}</b> |
|---|
| 375 | ... </div>''') |
|---|
| 376 | >>> print tmpl.generate(foo=True, bar='Hello') |
|---|
| 377 | <div> |
|---|
| 378 | <b>Hello</b> |
|---|
| 379 | </div> |
|---|
| 380 | """ |
|---|
| 381 | __slots__ = [] |
|---|
| 382 | |
|---|
| 383 | ATTRIBUTE = 'test' |
|---|
| 384 | |
|---|
| 385 | def __call__(self, stream, ctxt, directives): |
|---|
| 386 | if self.expr.evaluate(ctxt): |
|---|
| 387 | return _apply_directives(stream, ctxt, directives) |
|---|
| 388 | return [] |
|---|
| 389 | |
|---|
| 390 | |
|---|
| 391 | class MatchDirective(Directive): |
|---|
| 392 | """Implementation of the ``py:match`` template directive. |
|---|
| 393 | |
|---|
| 394 | >>> from genshi.template import MarkupTemplate |
|---|
| 395 | >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 396 | ... <span py:match="greeting"> |
|---|
| 397 | ... Hello ${select('@name')} |
|---|
| 398 | ... </span> |
|---|
| 399 | ... <greeting name="Dude" /> |
|---|
| 400 | ... </div>''') |
|---|
| 401 | >>> print tmpl.generate() |
|---|
| 402 | <div> |
|---|
| 403 | <span> |
|---|
| 404 | Hello Dude |
|---|
| 405 | </span> |
|---|
| 406 | </div> |
|---|
| 407 | """ |
|---|
| 408 | __slots__ = ['path', 'namespaces'] |
|---|
| 409 | |
|---|
| 410 | ATTRIBUTE = 'path' |
|---|
| 411 | |
|---|
| 412 | def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1): |
|---|
| 413 | Directive.__init__(self, None, template, namespaces, lineno, offset) |
|---|
| 414 | self.path = Path(value, template.filepath, lineno) |
|---|
| 415 | self.namespaces = namespaces or {} |
|---|
| 416 | |
|---|
| 417 | def __call__(self, stream, ctxt, directives): |
|---|
| 418 | ctxt._match_templates.append((self.path.test(ignore_context=True), |
|---|
| 419 | self.path, list(stream), self.namespaces, |
|---|
| 420 | directives)) |
|---|
| 421 | return [] |
|---|
| 422 | |
|---|
| 423 | def __repr__(self): |
|---|
| 424 | return '<%s "%s">' % (self.__class__.__name__, self.path.source) |
|---|
| 425 | |
|---|
| 426 | |
|---|
| 427 | class ReplaceDirective(Directive): |
|---|
| 428 | """Implementation of the ``py:replace`` template directive. |
|---|
| 429 | |
|---|
| 430 | This directive replaces the element with the result of evaluating the |
|---|
| 431 | value of the ``py:replace`` attribute: |
|---|
| 432 | |
|---|
| 433 | >>> from genshi.template import MarkupTemplate |
|---|
| 434 | >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 435 | ... <span py:replace="bar">Hello</span> |
|---|
| 436 | ... </div>''') |
|---|
| 437 | >>> print tmpl.generate(bar='Bye') |
|---|
| 438 | <div> |
|---|
| 439 | Bye |
|---|
| 440 | </div> |
|---|
| 441 | |
|---|
| 442 | This directive is equivalent to ``py:content`` combined with ``py:strip``, |
|---|
| 443 | providing a less verbose way to achieve the same effect: |
|---|
| 444 | |
|---|
| 445 | >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 446 | ... <span py:content="bar" py:strip="">Hello</span> |
|---|
| 447 | ... </div>''') |
|---|
| 448 | >>> print tmpl.generate(bar='Bye') |
|---|
| 449 | <div> |
|---|
| 450 | Bye |
|---|
| 451 | </div> |
|---|
| 452 | """ |
|---|
| 453 | __slots__ = [] |
|---|
| 454 | |
|---|
| 455 | def attach(cls, template, stream, value, namespaces, pos): |
|---|
| 456 | if not value: |
|---|
| 457 | raise TemplateSyntaxError('missing value for "replace" directive', |
|---|
| 458 | template.filepath, *pos[1:]) |
|---|
| 459 | expr = cls._parse_expr(value, template, *pos[1:]) |
|---|
| 460 | return None, [(EXPR, expr, pos)] |
|---|
| 461 | attach = classmethod(attach) |
|---|
| 462 | |
|---|
| 463 | |
|---|
| 464 | class StripDirective(Directive): |
|---|
| 465 | """Implementation of the ``py:strip`` template directive. |
|---|
| 466 | |
|---|
| 467 | When the value of the ``py:strip`` attribute evaluates to ``True``, the |
|---|
| 468 | element is stripped from the output |
|---|
| 469 | |
|---|
| 470 | >>> from genshi.template import MarkupTemplate |
|---|
| 471 | >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 472 | ... <div py:strip="True"><b>foo</b></div> |
|---|
| 473 | ... </div>''') |
|---|
| 474 | >>> print tmpl.generate() |
|---|
| 475 | <div> |
|---|
| 476 | <b>foo</b> |
|---|
| 477 | </div> |
|---|
| 478 | |
|---|
| 479 | Leaving the attribute value empty is equivalent to a truth value. |
|---|
| 480 | |
|---|
| 481 | This directive is particulary interesting for named template functions or |
|---|
| 482 | match templates that do not generate a top-level element: |
|---|
| 483 | |
|---|
| 484 | >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 485 | ... <div py:def="echo(what)" py:strip=""> |
|---|
| 486 | ... <b>${what}</b> |
|---|
| 487 | ... </div> |
|---|
| 488 | ... ${echo('foo')} |
|---|
| 489 | ... </div>''') |
|---|
| 490 | >>> print tmpl.generate() |
|---|
| 491 | <div> |
|---|
| 492 | <b>foo</b> |
|---|
| 493 | </div> |
|---|
| 494 | """ |
|---|
| 495 | __slots__ = [] |
|---|
| 496 | |
|---|
| 497 | def __call__(self, stream, ctxt, directives): |
|---|
| 498 | def _generate(): |
|---|
| 499 | if self.expr.evaluate(ctxt): |
|---|
| 500 | stream.next() # skip start tag |
|---|
| 501 | previous = stream.next() |
|---|
| 502 | for event in stream: |
|---|
| 503 | yield previous |
|---|
| 504 | previous = event |
|---|
| 505 | else: |
|---|
| 506 | for event in stream: |
|---|
| 507 | yield event |
|---|
| 508 | return _apply_directives(_generate(), ctxt, directives) |
|---|
| 509 | |
|---|
| 510 | def attach(cls, template, stream, value, namespaces, pos): |
|---|
| 511 | if not value: |
|---|
| 512 | return None, stream[1:-1] |
|---|
| 513 | return super(StripDirective, cls).attach(template, stream, value, |
|---|
| 514 | namespaces, pos) |
|---|
| 515 | attach = classmethod(attach) |
|---|
| 516 | |
|---|
| 517 | |
|---|
| 518 | class ChooseDirective(Directive): |
|---|
| 519 | """Implementation of the ``py:choose`` directive for conditionally selecting |
|---|
| 520 | one of several body elements to display. |
|---|
| 521 | |
|---|
| 522 | If the ``py:choose`` expression is empty the expressions of nested |
|---|
| 523 | ``py:when`` directives are tested for truth. The first true ``py:when`` |
|---|
| 524 | body is output. If no ``py:when`` directive is matched then the fallback |
|---|
| 525 | directive ``py:otherwise`` will be used. |
|---|
| 526 | |
|---|
| 527 | >>> from genshi.template import MarkupTemplate |
|---|
| 528 | >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/" |
|---|
| 529 | ... py:choose=""> |
|---|
| 530 | ... <span py:when="0 == 1">0</span> |
|---|
| 531 | ... <span py:when="1 == 1">1</span> |
|---|
| 532 | ... <span py:otherwise="">2</span> |
|---|
| 533 | ... </div>''') |
|---|
| 534 | >>> print tmpl.generate() |
|---|
| 535 | <div> |
|---|
| 536 | <span>1</span> |
|---|
| 537 | </div> |
|---|
| 538 | |
|---|
| 539 | If the ``py:choose`` directive contains an expression, the nested |
|---|
| 540 | ``py:when`` directives are tested for equality to the ``py:choose`` |
|---|
| 541 | expression: |
|---|
| 542 | |
|---|
| 543 | >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/" |
|---|
| 544 | ... py:choose="2"> |
|---|
| 545 | ... <span py:when="1">1</span> |
|---|
| 546 | ... <span py:when="2">2</span> |
|---|
| 547 | ... </div>''') |
|---|
| 548 | >>> print tmpl.generate() |
|---|
| 549 | <div> |
|---|
| 550 | <span>2</span> |
|---|
| 551 | </div> |
|---|
| 552 | |
|---|
| 553 | Behavior is undefined if a ``py:choose`` block contains content outside a |
|---|
| 554 | ``py:when`` or ``py:otherwise`` block. Behavior is also undefined if a |
|---|
| 555 | ``py:otherwise`` occurs before ``py:when`` blocks. |
|---|
| 556 | """ |
|---|
| 557 | __slots__ = ['matched', 'value'] |
|---|
| 558 | |
|---|
| 559 | ATTRIBUTE = 'test' |
|---|
| 560 | |
|---|
| 561 | def __call__(self, stream, ctxt, directives): |
|---|
| 562 | frame = dict({'_choose.matched': False}) |
|---|
| 563 | if self.expr: |
|---|
| 564 | frame['_choose.value'] = self.expr.evaluate(ctxt) |
|---|
| 565 | ctxt.push(frame) |
|---|
| 566 | for event in _apply_directives(stream, ctxt, directives): |
|---|
| 567 | yield event |
|---|
| 568 | ctxt.pop() |
|---|
| 569 | |
|---|
| 570 | |
|---|
| 571 | class WhenDirective(Directive): |
|---|
| 572 | """Implementation of the ``py:when`` directive for nesting in a parent with |
|---|
| 573 | the ``py:choose`` directive. |
|---|
| 574 | |
|---|
| 575 | See the documentation of the `ChooseDirective` for usage. |
|---|
| 576 | """ |
|---|
| 577 | __slots__ = ['filename'] |
|---|
| 578 | |
|---|
| 579 | ATTRIBUTE = 'test' |
|---|
| 580 | |
|---|
| 581 | def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1): |
|---|
| 582 | Directive.__init__(self, value, template, namespaces, lineno, offset) |
|---|
| 583 | self.filename = template.filepath |
|---|
| 584 | |
|---|
| 585 | def __call__(self, stream, ctxt, directives): |
|---|
| 586 | matched, frame = ctxt._find('_choose.matched') |
|---|
| 587 | if not frame: |
|---|
| 588 | raise TemplateRuntimeError('"when" directives can only be used ' |
|---|
| 589 | 'inside a "choose" directive', |
|---|
| 590 | self.filename, *stream.next()[2][1:]) |
|---|
| 591 | if matched: |
|---|
| 592 | return [] |
|---|
| 593 | if not self.expr and '_choose.value' not in frame: |
|---|
| 594 | raise TemplateRuntimeError('either "choose" or "when" directive ' |
|---|
| 595 | 'must have a test expression', |
|---|
| 596 | self.filename, *stream.next()[2][1:]) |
|---|
| 597 | if '_choose.value' in frame: |
|---|
| 598 | value = frame['_choose.value'] |
|---|
| 599 | if self.expr: |
|---|
| 600 | matched = value == self.expr.evaluate(ctxt) |
|---|
| 601 | else: |
|---|
| 602 | matched = bool(value) |
|---|
| 603 | else: |
|---|
| 604 | matched = bool(self.expr.evaluate(ctxt)) |
|---|
| 605 | frame['_choose.matched'] = matched |
|---|
| 606 | if not matched: |
|---|
| 607 | return [] |
|---|
| 608 | |
|---|
| 609 | return _apply_directives(stream, ctxt, directives) |
|---|
| 610 | |
|---|
| 611 | |
|---|
| 612 | class OtherwiseDirective(Directive): |
|---|
| 613 | """Implementation of the ``py:otherwise`` directive for nesting in a parent |
|---|
| 614 | with the ``py:choose`` directive. |
|---|
| 615 | |
|---|
| 616 | See the documentation of `ChooseDirective` for usage. |
|---|
| 617 | """ |
|---|
| 618 | __slots__ = ['filename'] |
|---|
| 619 | |
|---|
| 620 | def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1): |
|---|
| 621 | Directive.__init__(self, None, template, namespaces, lineno, offset) |
|---|
| 622 | self.filename = template.filepath |
|---|
| 623 | |
|---|
| 624 | def __call__(self, stream, ctxt, directives): |
|---|
| 625 | matched, frame = ctxt._find('_choose.matched') |
|---|
| 626 | if not frame: |
|---|
| 627 | raise TemplateRuntimeError('an "otherwise" directive can only be ' |
|---|
| 628 | 'used inside a "choose" directive', |
|---|
| 629 | self.filename, *stream.next()[2][1:]) |
|---|
| 630 | if matched: |
|---|
| 631 | return [] |
|---|
| 632 | frame['_choose.matched'] = True |
|---|
| 633 | |
|---|
| 634 | return _apply_directives(stream, ctxt, directives) |
|---|
| 635 | |
|---|
| 636 | |
|---|
| 637 | class WithDirective(Directive): |
|---|
| 638 | """Implementation of the ``py:with`` template directive, which allows |
|---|
| 639 | shorthand access to variables and expressions. |
|---|
| 640 | |
|---|
| 641 | >>> from genshi.template import MarkupTemplate |
|---|
| 642 | >>> tmpl = MarkupTemplate('''<div xmlns:py="http://genshi.edgewall.org/"> |
|---|
| 643 | ... <span py:with="y=7; z=x+10">$x $y $z</span> |
|---|
| 644 | ... </div>''') |
|---|
| 645 | >>> print tmpl.generate(x=42) |
|---|
| 646 | <div> |
|---|
| 647 | <span>42 7 52</span> |
|---|
| 648 | </div> |
|---|
| 649 | """ |
|---|
| 650 | __slots__ = ['vars'] |
|---|
| 651 | |
|---|
| 652 | ATTRIBUTE = 'vars' |
|---|
| 653 | |
|---|
| 654 | def __init__(self, value, template, namespaces=None, lineno=-1, offset=-1): |
|---|
| 655 | Directive.__init__(self, None, template, namespaces, lineno, offset) |
|---|
| 656 | self.vars = [] |
|---|
| 657 | value = value.strip() |
|---|
| 658 | try: |
|---|
| 659 | ast = _parse(value, 'exec').node |
|---|
| 660 | for node in ast.nodes: |
|---|
| 661 | if isinstance(node, compiler.ast.Discard): |
|---|
| 662 | continue |
|---|
| 663 | elif not isinstance(node, compiler.ast.Assign): |
|---|
| 664 | raise TemplateSyntaxError('only assignment allowed in ' |
|---|
| 665 | 'value of the "with" directive', |
|---|
| 666 | template.filepath, lineno, offset) |
|---|
| 667 | self.vars.append(([_assignment(n) for n in node.nodes], |
|---|
| 668 | Expression(node.expr, template.filepath, |
|---|
| 669 | lineno, lookup=template.lookup))) |
|---|
| 670 | except SyntaxError, err: |
|---|
| 671 | err.msg += ' in expression "%s" of "%s" directive' % (value, |
|---|
| 672 | self.tagname) |
|---|
| 673 | raise TemplateSyntaxError(err, template.filepath, lineno, |
|---|
| 674 | offset + (err.offset or 0)) |
|---|
| 675 | |
|---|
| 676 | def __call__(self, stream, ctxt, directives): |
|---|
| 677 | frame = {} |
|---|
| 678 | ctxt.push(frame) |
|---|
| 679 | for targets, expr in self.vars: |
|---|
| 680 | value = expr.evaluate(ctxt) |
|---|
| 681 | for assign in targets: |
|---|
| 682 | assign(frame, value) |
|---|
| 683 | for event in _apply_directives(stream, ctxt, directives): |
|---|
| 684 | yield event |
|---|
| 685 | ctxt.pop() |
|---|
| 686 | |
|---|
| 687 | def __repr__(self): |
|---|
| 688 | return '<%s>' % (self.__class__.__name__) |
|---|