Mercurial > genshi > mirror
comparison markup/template.py @ 53:512eb72dbb19 trunk
* Add helper function to let directives apply any remaining directives, and use that helper consistently in every directive.
* Fix the order of the `py:choose`, `py:when`, and `py:otherwise` directives.
* Moved some of the `py:choose` tests to a new `unittest` suite to keep the docstring compact.
author | cmlenz |
---|---|
date | Tue, 04 Jul 2006 11:57:08 +0000 |
parents | b2383634ec04 |
children | 1f3cd91325d9 |
comparison
equal
deleted
inserted
replaced
52:1340e3297d19 | 53:512eb72dbb19 |
---|---|
174 __slots__ = ['expr'] | 174 __slots__ = ['expr'] |
175 | 175 |
176 def __init__(self, value): | 176 def __init__(self, value): |
177 self.expr = value and Expression(value) or None | 177 self.expr = value and Expression(value) or None |
178 | 178 |
179 def __call__(self, stream, ctxt, directives=None): | 179 def __call__(self, stream, ctxt, directives): |
180 raise NotImplementedError | 180 raise NotImplementedError |
181 | 181 |
182 def __repr__(self): | 182 def __repr__(self): |
183 expr = '' | 183 expr = '' |
184 if self.expr is not None: | 184 if self.expr is not None: |
185 expr = ' "%s"' % self.expr.source | 185 expr = ' "%s"' % self.expr.source |
186 return '<%s%s>' % (self.__class__.__name__, expr) | 186 return '<%s%s>' % (self.__class__.__name__, expr) |
187 | |
188 def _apply_directives(self, stream, ctxt, directives): | |
189 if directives: | |
190 stream = directives[0](iter(stream), ctxt, directives[1:]) | |
191 return stream | |
187 | 192 |
188 | 193 |
189 class AttrsDirective(Directive): | 194 class AttrsDirective(Directive): |
190 """Implementation of the `py:attrs` template directive. | 195 """Implementation of the `py:attrs` template directive. |
191 | 196 |
210 <li>Bar</li> | 215 <li>Bar</li> |
211 </ul> | 216 </ul> |
212 """ | 217 """ |
213 __slots__ = [] | 218 __slots__ = [] |
214 | 219 |
215 def __call__(self, stream, ctxt, directives=None): | 220 def __call__(self, stream, ctxt, directives): |
216 kind, (tag, attrib), pos = stream.next() | 221 def _generate(): |
217 attrs = self.expr.evaluate(ctxt) | 222 kind, (tag, attrib), pos = stream.next() |
218 if attrs: | 223 attrs = self.expr.evaluate(ctxt) |
219 attrib = Attributes(attrib[:]) | 224 if attrs: |
220 if not isinstance(attrs, list): # assume it's a dict | 225 attrib = Attributes(attrib[:]) |
221 attrs = attrs.items() | 226 if not isinstance(attrs, list): # assume it's a dict |
222 for name, value in attrs: | 227 attrs = attrs.items() |
223 if value is None: | 228 for name, value in attrs: |
224 attrib.remove(name) | 229 if value is None: |
225 else: | 230 attrib.remove(name) |
226 attrib.set(name, unicode(value).strip()) | 231 else: |
227 yield kind, (tag, attrib), pos | 232 attrib.set(name, unicode(value).strip()) |
228 for event in stream: | 233 yield kind, (tag, attrib), pos |
229 yield event | 234 for event in stream: |
235 yield event | |
236 return self._apply_directives(_generate(), ctxt, directives) | |
230 | 237 |
231 | 238 |
232 class ContentDirective(Directive): | 239 class ContentDirective(Directive): |
233 """Implementation of the `py:content` template directive. | 240 """Implementation of the `py:content` template directive. |
234 | 241 |
245 </ul> | 252 </ul> |
246 """ | 253 """ |
247 __slots__ = [] | 254 __slots__ = [] |
248 | 255 |
249 def __call__(self, stream, ctxt, directives): | 256 def __call__(self, stream, ctxt, directives): |
250 def generate(): | 257 def _generate(): |
251 kind, data, pos = stream.next() | 258 kind, data, pos = stream.next() |
252 if kind is Stream.START: | 259 if kind is Stream.START: |
253 yield kind, data, pos # emit start tag | 260 yield kind, data, pos # emit start tag |
254 yield Template.EXPR, self.expr, pos | 261 yield Template.EXPR, self.expr, pos |
255 previous = stream.next() | 262 previous = stream.next() |
256 for event in stream: | 263 for event in stream: |
257 previous = event | 264 previous = event |
258 if previous is not None: | 265 if previous is not None: |
259 yield previous | 266 yield previous |
260 output = generate() | 267 return self._apply_directives(_generate(), ctxt, directives) |
261 if directives: | |
262 output = directives[0](output, ctxt, directives[1:]) | |
263 return output | |
264 | 268 |
265 | 269 |
266 class DefDirective(Directive): | 270 class DefDirective(Directive): |
267 """Implementation of the `py:def` template directive. | 271 """Implementation of the `py:def` template directive. |
268 | 272 |
335 if args: | 339 if args: |
336 scope[name] = args.pop(0) | 340 scope[name] = args.pop(0) |
337 else: | 341 else: |
338 scope[name] = kwargs.pop(name, self.defaults.get(name)) | 342 scope[name] = kwargs.pop(name, self.defaults.get(name)) |
339 ctxt.push(**scope) | 343 ctxt.push(**scope) |
340 stream = iter(self.stream) | 344 stream = self._apply_directives(self.stream, ctxt, self.directives) |
341 if self.directives: | |
342 stream = self.directives[0](stream, ctxt, self.directives[1:]) | |
343 for event in stream: | 345 for event in stream: |
344 yield event | 346 yield event |
345 ctxt.pop() | 347 ctxt.pop() |
346 | 348 |
347 | 349 |
364 targets, value = value.split(' in ', 1) | 366 targets, value = value.split(' in ', 1) |
365 self.targets = [str(name.strip()) for name in targets.split(',')] | 367 self.targets = [str(name.strip()) for name in targets.split(',')] |
366 Directive.__init__(self, value) | 368 Directive.__init__(self, value) |
367 | 369 |
368 def __call__(self, stream, ctxt, directives): | 370 def __call__(self, stream, ctxt, directives): |
369 iterable = self.expr.evaluate(ctxt) or [] | 371 iterable = self.expr.evaluate(ctxt) |
370 if iterable is not None: | 372 if iterable is not None: |
371 stream = list(stream) | 373 stream = list(stream) |
372 for item in iter(iterable): | 374 for item in iter(iterable): |
373 if len(self.targets) == 1: | 375 if len(self.targets) == 1: |
374 item = [item] | 376 item = [item] |
375 scope = {} | 377 scope = {} |
376 for idx, name in enumerate(self.targets): | 378 for idx, name in enumerate(self.targets): |
377 scope[name] = item[idx] | 379 scope[name] = item[idx] |
378 ctxt.push(**scope) | 380 ctxt.push(**scope) |
379 output = stream | 381 for event in self._apply_directives(stream, ctxt, directives): |
380 if directives: | |
381 output = directives[0](iter(output), ctxt, directives[1:]) | |
382 for event in output: | |
383 yield event | 382 yield event |
384 ctxt.pop() | 383 ctxt.pop() |
385 | 384 |
386 def __repr__(self): | 385 def __repr__(self): |
387 return '<%s "%s in %s">' % (self.__class__.__name__, | 386 return '<%s "%s in %s">' % (self.__class__.__name__, |
403 """ | 402 """ |
404 __slots__ = [] | 403 __slots__ = [] |
405 | 404 |
406 def __call__(self, stream, ctxt, directives): | 405 def __call__(self, stream, ctxt, directives): |
407 if self.expr.evaluate(ctxt): | 406 if self.expr.evaluate(ctxt): |
408 if directives: | 407 return self._apply_directives(stream, ctxt, directives) |
409 stream = directives[0](stream, ctxt, directives[1:]) | |
410 return stream | |
411 return [] | 408 return [] |
412 | 409 |
413 | 410 |
414 class MatchDirective(Directive): | 411 class MatchDirective(Directive): |
415 """Implementation of the `py:match` template directive. | 412 """Implementation of the `py:match` template directive. |
513 def __call__(self, stream, ctxt, directives=None): | 510 def __call__(self, stream, ctxt, directives=None): |
514 if self.expr: | 511 if self.expr: |
515 strip = self.expr.evaluate(ctxt) | 512 strip = self.expr.evaluate(ctxt) |
516 else: | 513 else: |
517 strip = True | 514 strip = True |
515 stream = self._apply_directives(stream, ctxt, directives) | |
518 if strip: | 516 if strip: |
519 stream.next() # skip start tag | 517 stream.next() # skip start tag |
520 previous = stream.next() | 518 previous = stream.next() |
521 for event in stream: | 519 for event in stream: |
522 yield previous | 520 yield previous |
527 | 525 |
528 | 526 |
529 class ChooseDirective(Directive): | 527 class ChooseDirective(Directive): |
530 """Implementation of the `py:choose` directive for conditionally selecting | 528 """Implementation of the `py:choose` directive for conditionally selecting |
531 one of several body elements to display. | 529 one of several body elements to display. |
532 | 530 |
533 If the `py:choose` expression is empty the expressions of nested `py:when` | 531 If the `py:choose` expression is empty the expressions of nested `py:when` |
534 directives are tested for truth. The first true `py:when` body is output. | 532 directives are tested for truth. The first true `py:when` body is output. |
535 | 533 If no `py:when` directive is matched then the fallback directive |
534 `py:otherwise` will be used. | |
535 | |
536 >>> ctxt = Context() | 536 >>> ctxt = Context() |
537 >>> tmpl = Template('''<div xmlns:py="http://purl.org/kid/ns#" | 537 >>> tmpl = Template('''<div xmlns:py="http://purl.org/kid/ns#" |
538 ... py:choose=""> | 538 ... py:choose=""> |
539 ... <span py:when="0 == 1">0</span> | 539 ... <span py:when="0 == 1">0</span> |
540 ... <span py:when="1 == 1">1</span> | 540 ... <span py:when="1 == 1">1</span> |
541 ... <span py:otherwise="">2</span> | |
541 ... </div>''') | 542 ... </div>''') |
542 >>> print tmpl.generate(ctxt) | 543 >>> print tmpl.generate(ctxt) |
543 <div> | 544 <div> |
544 <span>1</span> | 545 <span>1</span> |
545 </div> | 546 </div> |
546 | 547 |
547 If multiple `py:when` bodies match only the first is output. | |
548 >>> tmpl = Template('''<div xmlns:py="http://purl.org/kid/ns#" | |
549 ... py:choose=""> | |
550 ... <span py:when="1 == 1">1</span> | |
551 ... <span py:when="2 == 2">2</span> | |
552 ... </div>''') | |
553 >>> print tmpl.generate(ctxt) | |
554 <div> | |
555 <span>1</span> | |
556 </div> | |
557 | |
558 If the `py:choose` directive contains an expression, the nested `py:when` | 548 If the `py:choose` directive contains an expression, the nested `py:when` |
559 directives are tested for equality to the `py:choose` expression. | 549 directives are tested for equality to the `py:choose` expression: |
550 | |
560 >>> tmpl = Template('''<div xmlns:py="http://purl.org/kid/ns#" | 551 >>> tmpl = Template('''<div xmlns:py="http://purl.org/kid/ns#" |
561 ... py:choose="2"> | 552 ... py:choose="2"> |
562 ... <span py:when="1">1</span> | 553 ... <span py:when="1">1</span> |
563 ... <span py:when="2">2</span> | 554 ... <span py:when="2">2</span> |
564 ... </div>''') | 555 ... </div>''') |
565 >>> print tmpl.generate(ctxt) | 556 >>> print tmpl.generate(ctxt) |
566 <div> | 557 <div> |
567 <span>2</span> | 558 <span>2</span> |
568 </div> | 559 </div> |
569 | 560 |
570 If no `py:when` directive is matched then the fallback directive | |
571 `py:otherwise` will be used. | |
572 >>> tmpl = Template('''<div xmlns:py="http://purl.org/kid/ns#" | |
573 ... py:choose=""> | |
574 ... <span py:when="False">hidden</span> | |
575 ... <span py:otherwise="">hello</span> | |
576 ... </div>''') | |
577 >>> print tmpl.generate(ctxt) | |
578 <div> | |
579 <span>hello</span> | |
580 </div> | |
581 | |
582 `py:choose` blocks can be nested: | |
583 >>> tmpl = Template('''<div xmlns:py="http://purl.org/kid/ns#" | |
584 ... py:choose="1"> | |
585 ... <div py:when="1" py:choose="3"> | |
586 ... <span py:when="2">2</span> | |
587 ... <span py:when="3">3</span> | |
588 ... </div> | |
589 ... </div>''') | |
590 >>> print tmpl.generate(ctxt) | |
591 <div> | |
592 <div> | |
593 <span>3</span> | |
594 </div> | |
595 </div> | |
596 | |
597 Behavior is undefined if a `py:choose` block contains content outside a | 561 Behavior is undefined if a `py:choose` block contains content outside a |
598 `py:when` or `py:otherwise` block. Behavior is also undefined if a | 562 `py:when` or `py:otherwise` block. Behavior is also undefined if a |
599 `py:otherwise` occurs before `py:when` blocks. | 563 `py:otherwise` occurs before `py:when` blocks. |
600 """ | 564 """ |
601 __slots__ = ['matched', 'value'] | 565 __slots__ = ['matched', 'value'] |
602 | 566 |
603 def __call__(self, stream, ctxt, directives=None): | 567 def __call__(self, stream, ctxt, directives): |
604 if self.expr: | 568 if self.expr: |
605 self.value = self.expr.evaluate(ctxt) | 569 self.value = self.expr.evaluate(ctxt) |
606 self.matched = False | 570 self.matched = False |
607 ctxt.push(_choose=self) | 571 ctxt.push(_choose=self) |
608 for event in stream: | 572 for event in self._apply_directives(stream, ctxt, directives): |
609 yield event | 573 yield event |
610 ctxt.pop() | 574 ctxt.pop() |
611 | 575 |
612 | 576 |
613 class WhenDirective(Directive): | 577 class WhenDirective(Directive): |
622 return [] | 586 return [] |
623 value = self.expr.evaluate(ctxt) | 587 value = self.expr.evaluate(ctxt) |
624 try: | 588 try: |
625 if value == choose.value: | 589 if value == choose.value: |
626 choose.matched = True | 590 choose.matched = True |
627 return stream | 591 return self._apply_directives(stream, ctxt, directives) |
628 except AttributeError: | 592 except AttributeError: |
629 if value: | 593 if value: |
630 choose.matched = True | 594 choose.matched = True |
631 return stream | 595 return self._apply_directives(stream, ctxt, directives) |
632 return [] | 596 return [] |
633 | 597 |
634 | 598 |
635 class OtherwiseDirective(Directive): | 599 class OtherwiseDirective(Directive): |
636 """Implementation of the `py:otherwise` directive for nesting in a parent | 600 """Implementation of the `py:otherwise` directive for nesting in a parent |
641 def __call__(self, stream, ctxt, directives=None): | 605 def __call__(self, stream, ctxt, directives=None): |
642 choose = ctxt['_choose'] | 606 choose = ctxt['_choose'] |
643 if choose.matched: | 607 if choose.matched: |
644 return [] | 608 return [] |
645 choose.matched = True | 609 choose.matched = True |
646 return stream | 610 return self._apply_directives(stream, ctxt, directives) |
647 | 611 |
648 | 612 |
649 class Template(object): | 613 class Template(object): |
650 """Can parse a template and transform it into the corresponding output | 614 """Can parse a template and transform it into the corresponding output |
651 based on context data. | 615 based on context data. |
657 | 621 |
658 directives = [('def', DefDirective), | 622 directives = [('def', DefDirective), |
659 ('match', MatchDirective), | 623 ('match', MatchDirective), |
660 ('for', ForDirective), | 624 ('for', ForDirective), |
661 ('if', IfDirective), | 625 ('if', IfDirective), |
662 ('choose', ChooseDirective), | |
663 ('when', WhenDirective), | 626 ('when', WhenDirective), |
664 ('otherwise', OtherwiseDirective), | 627 ('otherwise', OtherwiseDirective), |
628 ('choose', ChooseDirective), | |
665 ('replace', ReplaceDirective), | 629 ('replace', ReplaceDirective), |
666 ('content', ContentDirective), | 630 ('content', ContentDirective), |
667 ('attrs', AttrsDirective), | 631 ('attrs', AttrsDirective), |
668 ('strip', StripDirective)] | 632 ('strip', StripDirective)] |
669 _dir_by_name = dict(directives) | 633 _dir_by_name = dict(directives) |