1# Copyright (c) 2004 Divmod.
2# See LICENSE for details.
3
4"""Page, Fragment and other standard renderers.
5
6This module contains classes and function responsible for rendering
7dynamic content and a few useful mixin classes for inheriting common
8functionality.
9
10Mostly, you'll use the renderers:
11
12 - B{Page} - Nevow's main resource type for rendering web pages and
13   locating child resource.
14 - B{Fragment} - useful for rendering more complex parts of a document
15   that require a set of data_* and render_* methods.
16 - B{sequence} - render each item in a sequence.
17 - B{mapping} - publish a dictionary by filling slots
18"""
19
20from time import time as now
21from cStringIO import StringIO
22import random
23import warnings
24
25from zope.interface import implements, providedBy
26
27import twisted.python.components as tpc
28from twisted.python.reflect import qual, accumulateClassList
29
30from nevow.context import WovenContext, NodeNotFound, PageContext
31from nevow import inevow, tags, flat, util, url
32from nevow.util import log
33
34import formless
35from formless import iformless, annotate
36
37
38def _getPreprocessors(inst):
39    """
40    Accumulate elements from the sequences bound at the C{preprocessors}
41    attribute on all classes in the inheritance hierarchy of the class of
42    C{inst}.  A C{preprocessors} attribute on the given instance overrides
43    all preprocessors from the class inheritance hierarchy.
44    """
45    if 'preprocessors' in vars(inst):
46        return inst.preprocessors
47    preprocessors = []
48    accumulateClassList(
49        inst.__class__,
50        'preprocessors',
51        preprocessors)
52    return preprocessors
53
54
55
56class RenderFactory(object):
57    implements(inevow.IRendererFactory)
58
59    def renderer(self, context, name):
60        """Return a renderer with the given name.
61        """
62
63        # The named renderer can be parameterised, i.e. 'renderIt one,two,three'
64        args = []
65        if name.find(' ') != -1:
66            name, args = name.split(None, 1)
67            args = [arg.strip() for arg in args.split(',')]
68
69        callable = getattr(self, 'render_%s' % name, None)
70        if callable is None:
71            warnings.warn(
72                "Renderer %r missing on %s will result in an exception." % (
73                    name, qual(type(self))),
74                category=DeprecationWarning,
75                stacklevel=1)
76            callable = lambda *a, **kw: context.tag[
77                "The renderer named '%s' was not found in %r." % (name, self)]
78
79        if args:
80            return callable(*args)
81
82        return callable
83
84    render_sequence = lambda self, context, data: sequence(context, data)
85    render_mapping = lambda self, context, data: mapping(context, data)
86    render_string = lambda self, context, data: string(context, data)
87    render_xml = lambda self, context, data: context.tag.clear()[tags.xml(data)]
88    render_data = lambda self, context, data_: data(context, data_)
89
90
91class MacroFactory(object):
92    implements(inevow.IMacroFactory)
93
94    def macro(self, ctx, name):
95        """Return a macro with the given name.
96        """
97        # The named macro can be parameterized, i.e. 'macroFoo foo,bar,baz'
98        args = []
99        if name.find(' ') != -1:
100            name, args = name.split(None, 1)
101            args = [arg.strip() for arg in args.split(',')]
102
103        callable = getattr(self, 'macro_%s' % name, None)
104        if callable is None:
105            callable = lambda ctx, *args: ctx.tag[
106                "The macro named '%s' was not found in %r." % (name, self)]
107
108        if args:
109            ## Macros are expanded in TagSerializer by calling them with a single arg, the context
110            return lambda ctx: callable(ctx, *args)
111
112        return callable
113
114
115class DataNotFoundError(Exception):
116    """Raised when a data directive could not be resolved on the page or its
117    original attribute by the DataFactory.
118    """
119
120
121class DataFactory(object):
122    implements(inevow.IContainer)
123
124    def child(self, context, n):
125        args = []
126        if n.find(' ') != -1:
127            name, args = n.split(None, 1)
128            args = [arg.strip() for arg in args.split(',')]
129        else:
130            name = n
131
132        callable = getattr(self, 'data_%s' % name, None)
133        ## If this page doesn't have an appropriate data_* method...
134        if callable is None:
135            ## See if our self.original has an IContainer...
136            container = inevow.IContainer(self.original, None)
137            if container is None:
138                raise DataNotFoundError("The data named %r was not found in %r." % (name, self))
139            else:
140                ## And delegate to it if so.
141                return container.child(context, n)
142
143        if args:
144            return callable(*args)
145
146        return callable
147
148
149class FreeformChildMixin:
150    """Mixin that handles locateChild for freeform segments."""
151    def locateChild(self, ctx, segments):
152        request = inevow.IRequest(ctx)
153        ## The method or property name we are going to validate against/affect
154        bindingName = None
155
156        name = segments[0]
157        if name.startswith('freeform_post!'):
158            configurableName, bindingName = name.split('!')[1:3]
159        elif name.startswith('freeform-action-post!'):
160            configurableName, request.args['freeform-actee'] = name.split('!')[1:3]
161            bindingName = request.args['freeform-action'][0]
162        if bindingName:
163            ctx.remember(self, inevow.IResource)
164            ctx.remember(request, inevow.IRequest)
165            cf = iformless.IConfigurableFactory(self)
166            def checkC(c):
167                if c is not None:
168                    return self.webFormPost(request, self, c, ctx, bindingName, request.args)
169            return util.maybeDeferred(cf.locateConfigurable, ctx, configurableName).addCallback(checkC)
170        return NotFound
171
172    def child_freeform_hand(self, ctx):
173        carryoverHand = inevow.IHand(ctx, None)
174        if carryoverHand is not None:
175            inevow.ISession(ctx).setComponent(inevow.IHand, carryoverHand)
176            return carryoverHand
177        return inevow.IHand(inevow.ISession(ctx), None)
178
179
180class ConfigurableMixin(object):
181    """
182    A sane L{IConfigurable} implementation for L{Fragment} and L{Page}.
183
184    Methods starting with C{bind_} automatically expose corresponding method
185    names.  C{bind_*} should return an L{IBinding} (L{PropertyBinding} or
186    L{MethodBinding}), or, as a shortcut for L{MethodBinding}, a C{list} of
187    two-C{tuples} like this::
188
189        def bind_foo(self, ctx):
190            return [('argName', String()), ('anotherArg', Integer())]
191
192        def foo(self, argName, anotherArg):
193            assert isinstance(argName, str)
194            assert isinstance(anotherArg, int)
195    """
196    implements(iformless.IConfigurable)
197
198    def getBindingNames(self, ctx):
199        """Expose bind_* methods and attributes on this class.
200        """
201        for name in dir(self):
202            if name.startswith('bind_'):
203                yield name[len('bind_'):]
204
205    def getBinding(self, ctx, name):
206        """Massage bind_* methods and attributes into an
207        IBinding. The bind_* method or attribute can either
208        already implement IBinding or be a list of twoples
209        which will be massaged into a MethodBinding as
210        described in the ConfigurableMixin class docstring.
211        """
212        def _get_binding(binding):
213            if callable(binding):
214                binding = util.maybeDeferred(binding, ctx)
215            return binding
216
217        def _convert_list(binding):
218            if isinstance(binding, list):
219                binding = annotate.MethodBinding(
220                    name, annotate.Method(arguments=[
221                    annotate.Argument(n, v, v.id)
222                    for (n, v) in binding]))
223            return binding
224
225        binding = util.maybeDeferred(getattr, self, 'bind_%s' % name)
226        return binding.addCallback(_get_binding).addCallback(_convert_list)
227
228    def getDefault(self, forBinding):
229        """Get a default value for a given binding. If the
230        binding is a Property, get the current value of
231        that property off self. If not, simply return
232        forBinding.default.
233        """
234        ## If it is a Property, get the value off self
235        if not isinstance(forBinding, annotate.Argument):
236            if hasattr(self, forBinding.name):
237                return getattr(self, forBinding.name)
238        return forBinding.default
239
240    def postForm(self, ctx, bindingName, args):
241        """Accept a form post to the given bindingName.
242        The post arguments are given in args.
243
244        This will invoke the IInputProcessor for the
245        binding with the given name. If it succeeds, the
246        property will be modified or the method will have
247        been called. If it fails, a ValidateError exception
248        will be raised.
249        """
250        def _callback(binding):
251            ctx.remember(binding, iformless.IBinding)
252            ctx.remember(self, iformless.IConfigurable)
253            rv = iformless.IInputProcessor(binding).process(ctx, self, args)
254            ctx.remember(rv, inevow.IHand)
255            ctx.remember('%r success.' % bindingName, inevow.IStatusMessage)
256            return rv
257        return util.maybeDeferred(self.getBinding, ctx,
258                                  bindingName).addCallback(_callback)
259
260
261class ConfigurableFactory:
262    """Locates configurables by looking for methods that start with
263    configurable_ and end with the name of the configurable. The method
264    should take a single arg (other than self) - the current context.
265    """
266    implements(iformless.IConfigurableFactory)
267
268    def locateConfigurable(self, context, name):
269        """formless.webform.renderForms calls locateConfigurable on the IConfigurableFactory
270        instance it retrieves from the context. It passes the "name" that was passed to it,
271        so if renderForms() was placed in the DOM, locateConfigurable will be called with
272        name = ''; if renderForms('foo') was placed in the DOM, locateConfigurable will
273        be called with name = 'foo'.
274
275        This default implementation of locateConfigurable looks for a configurable_* method
276        corresponding to the name which was passed.
277        """
278        return util.maybeDeferred(getattr(self, 'configurable_%s'%name),
279                                  context).addCallback(iformless.IConfigurable)
280
281    def configurable_(self, context):
282        """Configurable factory for use when self is a configurable;
283        aka it implements IConfigurable or one or more TypedInterface
284        subclasses. Usage:
285
286        >>> class IFoo(TypedInterface):
287        ...     def bar(): pass
288        ...     bar = autocallable(bar)
289        ...
290        >>> class Foo(Page):
291        ...     implements(IFoo)
292        ...
293        ...     def bar():
294        ...         print "bar called through the web!"
295        ...
296        ...     def render_forms(self, ctx, data):
297        ...         return renderForms() # or renderForms('')
298        ...
299        ...     docFactory = stan(render_forms).
300        """
301        if filter(lambda x: issubclass(x, annotate.TypedInterface), providedBy(self)):
302            warnings.warn("[0.5] Subclassing TypedInterface to declare annotations is deprecated. Please provide bind_* methods on your Page or Fragment subclass instead.", DeprecationWarning)
303            from formless import configurable
304            return configurable.TypedInterfaceConfigurable(self)
305        return self
306
307    def configurable_original(self, ctx):
308        """Configurable factory for use when self.original is a configurable;
309        aka it implements IConfigurable or one or more TypedInterface
310        subclasses. Usage:
311
312
313        >>> class Foo(Page):
314        ...     def __init__(self):
315        ...         self.original = SomeConfigurable()
316        ...     def render_forms(self, ctx, data):
317        ...         return renderForms('original')
318        ...     docFactory = stan(render_forms)
319        """
320        return self.original
321
322_CARRYOVER = {}
323
324
325def defaultsFactory(ctx):
326    co = _CARRYOVER.get(
327        ctx.tag.args.get('_nevow_carryover_', [None])[0], None)
328    from formless import webform
329    defaults = webform.FormDefaults()
330    if co is not None:
331        e = iformless.IFormErrors(co, {})
332        for k, v in e.items():
333            defaults.getAllDefaults(k).update(v.partialForm)
334    return defaults
335
336
337def errorsFactory(ctx):
338    co = _CARRYOVER.get(
339        ctx.tag.args.get('_nevow_carryover_', [None])[0], None)
340    from formless import webform
341    errs = webform.FormErrors()
342    if co is not None:
343        e = iformless.IFormErrors(co, {})
344        for k, v in e.items():
345            errs.updateErrors(k, v.errors)
346            errs.setError(k, v.formErrorMessage)
347    return errs
348
349
350def handFactory(ctx):
351    co = _CARRYOVER.get(
352        ctx.tag.args.get('_nevow_carryover_', [None])[0], None)
353    return inevow.IHand(co, None)
354
355
356def statusFactory(ctx):
357    co = _CARRYOVER.get(
358        ctx.tag.args.get('_nevow_carryover_', [None])[0], None)
359    return inevow.IStatusMessage(co, None)
360
361
362def originalFactory(ctx):
363    return ctx.tag
364
365
366class Fragment(DataFactory, RenderFactory, MacroFactory, ConfigurableMixin):
367    """
368    This class is deprecated because it relies on context objects
369    U{which are being removed from Nevow<http://divmod.org/trac/wiki/WitherContext>}.
370
371    @see: L{Element}
372    """
373    implements(inevow.IRenderer, inevow.IGettable)
374
375    docFactory = None
376    original = None
377
378    def __init__(self, original=None, docFactory=None):
379        if original is not None:
380            self.original = original
381        self.toremember = []
382        if docFactory is not None:
383            self.docFactory = docFactory
384
385    def get(self, context):
386        return self.original
387
388    def rend(self, context, data):
389        # Create a new context so the current context is not polluted with
390        # remembrances.
391        context = WovenContext(parent=context)
392
393        # Remember me as lots of things
394        self.rememberStuff(context)
395
396        preprocessors = _getPreprocessors(self)
397
398        # This tidbit is to enable us to include Page objects inside
399        # stan expressions and render_* methods and the like. But
400        # because of the way objects can get intertwined, we shouldn't
401        # leave the pattern changed.
402        old = self.docFactory.pattern
403        self.docFactory.pattern = 'content'
404        self.docFactory.precompiledDoc = None
405        try:
406            try:
407                doc = self.docFactory.load(context, preprocessors)
408            finally:
409                self.docFactory.pattern = old
410                self.docFactory.precompiledDoc = None
411        except TypeError, e:
412            # Avert your eyes now! I don't want to catch anything but IQ
413            # adaption exceptions here but all I get is TypeError. This whole
414            # section of code is a complete hack anyway so one more won't
415            # matter until it's all removed. ;-).
416            if 'nevow.inevow.IQ' not in str(e):
417                raise
418            doc = self.docFactory.load(context, preprocessors)
419        except NodeNotFound:
420            doc = self.docFactory.load(context, preprocessors)
421        else:
422            if old == 'content':
423                warnings.warn(
424                    """[v0.5] Using a Page with a 'content' pattern is
425                               deprecated.""",
426                    DeprecationWarning,
427                    stacklevel=2)
428
429        context.tag = tags.invisible[doc]
430        return context
431
432    def remember(self, obj, inter=None):
433        """Remember an object for an interface on new PageContexts which are
434        constructed around this Page. Whenever this Page is involved in object
435        traversal in the future, all objects will be visible to .locate() calls
436        at the level of a PageContext wrapped around this Page and all contexts
437        below it.
438
439        This does not affect existing Context instances.
440        """
441        self.toremember.append((obj, inter))
442
443    def rememberStuff(self, ctx):
444        ctx.remember(self, inevow.IRenderer)
445        ctx.remember(self, inevow.IRendererFactory)
446        ctx.remember(self, inevow.IMacroFactory)
447        ctx.remember(self, inevow.IData)
448
449
450class ChildLookupMixin(FreeformChildMixin):
451    ##
452    # IResource methods
453    ##
454
455    children = None
456    def locateChild(self, ctx, segments):
457        """Locate a child page of this one. ctx is a
458        nevow.context.PageContext representing the parent Page, and segments
459        is a tuple of each element in the URI. An tuple (page, segments) should be
460        returned, where page is an instance of nevow.rend.Page and segments a tuple
461        representing the remaining segments of the URI. If the child is not found, return
462        NotFound instead.
463
464        locateChild is designed to be easily overridden to perform fancy lookup tricks.
465        However, the default locateChild is useful, and looks for children in three places,
466        in this order:
467
468         - in a dictionary, self.children
469         - a member of self named child_<childname>. This can be either an
470           attribute or a method. If an attribute, it should be an object which
471           can be adapted to IResource. If a method, it should take the context
472           and return an object which can be adapted to IResource.
473         - by calling self.childFactory(ctx, name). Name is a single string instead
474           of a tuple of strings. This should return an object that can be adapted
475           to IResource.
476        """
477
478        if self.children is not None:
479            r = self.children.get(segments[0], None)
480            if r is not None:
481                return r, segments[1:]
482
483        w = getattr(self, 'child_%s'%segments[0], None)
484        if w is not None:
485            if inevow.IResource(w, None) is not None:
486                return w, segments[1:]
487            r = w(ctx)
488            if r is not None:
489                return r, segments[1:]
490
491        r = self.childFactory(ctx, segments[0])
492        if r is not None:
493            return r, segments[1:]
494
495        return FreeformChildMixin.locateChild(self, ctx, segments)
496
497    def childFactory(self, ctx, name):
498        """Used by locateChild to return children which are generated
499        dynamically. Note that higher level interfaces use only locateChild,
500        and only nevow.rend.Page.locateChild uses this.
501
502        segment is a string representing one element of the URI. Request is a
503        nevow.appserver.NevowRequest.
504
505        The default implementation of this always returns None; it is intended
506        to be overridden."""
507        return None
508
509    def putChild(self, name, child):
510        if self.children is None:
511            self.children = {}
512        self.children[name] = child
513
514
515class Page(Fragment, ConfigurableFactory, ChildLookupMixin):
516    """A page is the main Nevow resource and renders a document loaded
517    via the document factory (docFactory).
518    """
519
520    implements(inevow.IResource)
521
522    buffered = False
523
524    beforeRender = None
525    afterRender = None
526    addSlash = None
527
528    flattenFactory = lambda self, *args: flat.flattenFactory(*args)
529
530    def renderHTTP(self, ctx):
531        if self.beforeRender is not None:
532            return util.maybeDeferred(self.beforeRender,ctx).addCallback(
533                    lambda result,ctx: self._renderHTTP(ctx),ctx)
534        return self._renderHTTP(ctx)
535
536    def _renderHTTP(self, ctx):
537        request = inevow.IRequest(ctx)
538        ## XXX request is really ctx now, change the name here
539        if self.addSlash and inevow.ICurrentSegments(ctx)[-1] != '':
540            request.redirect(str(request.URLPath().child('')))
541            return ''
542
543        log.msg(http_render=None, uri=request.uri)
544
545        self.rememberStuff(ctx)
546
547        def finishRequest():
548            carryover = request.args.get('_nevow_carryover_', [None])[0]
549            if carryover is not None and _CARRYOVER.has_key(carryover):
550                del _CARRYOVER[carryover]
551            if self.afterRender is not None:
552                return util.maybeDeferred(self.afterRender,ctx)
553
554        if self.buffered:
555            io = StringIO()
556            writer = io.write
557            def finisher(result):
558                request.write(io.getvalue())
559                return util.maybeDeferred(finishRequest).addCallback(lambda r: result)
560        else:
561            writer = request.write
562            def finisher(result):
563                return util.maybeDeferred(finishRequest).addCallback(lambda r: result)
564
565        preprocessors = _getPreprocessors(self)
566        doc = self.docFactory.load(ctx, preprocessors)
567        ctx =  WovenContext(ctx, tags.invisible[doc])
568
569        return self.flattenFactory(doc, ctx, writer, finisher)
570
571    def rememberStuff(self, ctx):
572        Fragment.rememberStuff(self, ctx)
573        ctx.remember(self, inevow.IResource)
574
575    def renderString(self, ctx=None):
576        """Render this page outside of the context of a web request, returning
577        a Deferred which will result in a string.
578
579        If twisted is not installed, this method will return a string result immediately,
580        and this method is equivalent to renderSynchronously.
581        """
582        io = StringIO()
583        writer = io.write
584
585        def finisher(result):
586            return io.getvalue()
587
588        ctx = PageContext(parent=ctx, tag=self)
589        self.rememberStuff(ctx)
590        doc = self.docFactory.load(ctx)
591        ctx =  WovenContext(ctx, tags.invisible[doc])
592
593        return self.flattenFactory(doc, ctx, writer, finisher)
594
595    def renderSynchronously(self, ctx=None):
596        """Render this page synchronously, returning a string result immediately.
597        Raise an exception if a Deferred is required to complete the rendering
598        process.
599        """
600        io = StringIO()
601
602        ctx = PageContext(parent=ctx, tag=self)
603        self.rememberStuff(ctx)
604        doc = self.docFactory.load(ctx)
605        ctx =  WovenContext(ctx, tags.invisible[doc])
606
607        def raiseAlways(item):
608            raise NotImplementedError("renderSynchronously can not support"
609                " rendering: %s" % (item, ))
610
611        list(flat.iterflatten(doc, ctx, io.write, raiseAlways))
612
613        return io.getvalue()
614
615    def child_(self, ctx):
616        """When addSlash is True, a page rendered at a url with no
617        trailing slash and a page rendered at a url with a trailing
618        slash will be identical. addSlash is useful for the root
619        resource of a site or directory-like resources.
620        """
621        # Only allow an empty child, by default, if it's on the end
622        # and we're a directoryish resource (addSlash = True)
623        if self.addSlash and len(inevow.IRemainingSegments(ctx)) == 1:
624            return self
625        return None
626
627    def webFormPost(self, request, res, configurable, ctx, bindingName, args):
628        """Accept a web form post, either redisplaying the original form (with
629        errors) if validation fails, or redirecting to the appropriate location after
630        the post succeeds. This hook exists specifically for formless.
631
632        New in 0.5, _nevow_carryover_ is only used if an autocallable method
633        returns a result that needs to be carried over.
634
635        New in 0.5, autocallables may return a nevow.url.URL or URLOverlay
636        instance rather than setting IRedirectAfterPost on the request.
637
638        New in 0.5, autocallables may return a Page instance to have that Page
639        instance rendered at the post target URL with no redirects at all. Useful
640        for multi-step wizards.
641        """
642        def redirectAfterPost(aspects):
643            hand = aspects.get(inevow.IHand)
644            refpath = None
645            if hand is not None:
646                if isinstance(hand, Page):
647                    refpath = url.here
648                    if 'freeform_hand' not in inevow.IRequest(ctx).prepath:
649                        refpath = refpath.child('freeform_hand')
650                if isinstance(hand, (url.URL, url.URLOverlay)):
651                    refpath, hand = hand, None
652
653            if refpath is None:
654                redirectAfterPost = request.getComponent(iformless.IRedirectAfterPost, None)
655                if redirectAfterPost is None:
656                    ref = request.getHeader('referer')
657                    if ref:
658                        refpath = url.URL.fromString(ref)
659                    else:
660                        refpath = url.here
661                else:
662                    warnings.warn("[0.5] IRedirectAfterPost is deprecated. Return a URL instance from your autocallable instead.", DeprecationWarning, 2)
663                    ## Use the redirectAfterPost url
664                    ref = str(redirectAfterPost)
665                    refpath = url.URL.fromString(ref)
666
667            if hand is not None or aspects.get(iformless.IFormErrors) is not None:
668                magicCookie = '%s%s%s' % (now(),request.getClientIP(),random.random())
669                refpath = refpath.replace('_nevow_carryover_', magicCookie)
670                _CARRYOVER[magicCookie] = C = tpc.Componentized()
671                for k, v in aspects.iteritems():
672                    C.setComponent(k, v)
673
674            destination = flat.flatten(refpath, ctx)
675            request.redirect(destination)
676            from nevow import static
677            return static.Data('You posted a form to %s' % bindingName, 'text/plain'), ()
678
679        return util.maybeDeferred(
680            configurable.postForm, ctx, bindingName, args
681        ).addCallback(
682            self.onPostSuccess, request, ctx, bindingName, redirectAfterPost
683        ).addErrback(
684            self.onPostFailure, request, ctx, bindingName, redirectAfterPost
685        )
686
687    def onPostSuccess(self, result, request, ctx, bindingName, redirectAfterPost):
688        if result is None:
689            message = "%s success." % formless.nameToLabel(bindingName)
690        else:
691            message = result
692
693        return redirectAfterPost({inevow.IHand: result, inevow.IStatusMessage: message})
694
695    def onPostFailure(self, reason, request, ctx, bindingName, redirectAfterPost):
696        reason.trap(formless.ValidateError)
697        return redirectAfterPost({iformless.IFormErrors: {bindingName: reason.value}})
698
699
700def sequence(context, data):
701    """Renders each item in the sequence using patterns found in the
702    children of the element.
703
704    Sequence recognises the following patterns:
705
706     - header: Rendered at the start, before the first item. If multiple
707       header patterns are provided they are rendered together in the
708       order they were defined.
709
710     - footer: Just like the header only renderer at the end, after the
711       last item.
712
713     - item: Rendered once for each item in the sequence. If multiple
714       item patterns are provided then the pattern is cycled in the
715       order defined.
716
717     - divider: Rendered once between each item in the sequence. Multiple
718       divider patterns are cycled.
719
720     - empty: Rendered instead of item and divider patterns when the
721       sequence contains no items.
722
723    Example::
724
725     <table nevow:render="sequence" nevow:data="peopleSeq">
726       <tr nevow:pattern="header">
727         <th>name</th>
728         <th>email</th>
729       </tr>
730       <tr nevow:pattern="item" class="odd">
731         <td>name goes here</td>
732         <td>email goes here</td>
733       </tr>
734       <tr nevow:pattern="item" class="even">
735         <td>name goes here</td>
736         <td>email goes here</td>
737       </tr>
738       <tr nevow:pattern="empty">
739         <td colspan="2"><em>they've all gone!</em></td>
740       </tr>
741     </table>
742
743    """
744    tag = context.tag
745    headers = tag.allPatterns('header')
746    pattern = tag.patternGenerator('item')
747    divider = tag.patternGenerator('divider', default=tags.invisible)
748    content = [(pattern(data=element), divider(data=element)) for element in data]
749    if not content:
750        content = tag.allPatterns('empty')
751    else:
752        ## No divider after the last thing.
753        content[-1] = content[-1][0]
754    footers = tag.allPatterns('footer')
755
756    return tag.clear()[ headers, content, footers ]
757
758
759def mapping(context, data):
760    """Fills any slots in the element's children with data from a
761    dictionary. The dict keys are used as the slot names, the dict
762    values are used as filling.
763
764    Example::
765
766     <tr nevow:render="mapping" nevow:data="personDict">
767       <td><nevow:slot name="name"/></td>
768       <td><nevow:slot name="email"/></td>
769     </tr>
770    """
771    for k, v in data.items():
772        context.fillSlots(k, v)
773    return context.tag
774
775
776def string(context, data):
777    return context.tag.clear()[str(data)]
778
779
780def data(context, data):
781    """Replace the tag's content with the current data.
782    """
783    return context.tag.clear()[data]
784
785
786class FourOhFour:
787    """A simple 404 (not found) page.
788    """
789    implements(inevow.IResource)
790
791    notFound = "<html><head><title>Page Not Found</title></head><body>Sorry, but I couldn't find the object you requested.</body></html>"
792    original = None
793
794    def locateChild(self, ctx, segments):
795        return NotFound
796
797    def renderHTTP(self, ctx):
798        inevow.IRequest(ctx).setResponseCode(404)
799        # Look for an application-remembered handler
800        try:
801            notFoundHandler = ctx.locate(inevow.ICanHandleNotFound)
802        except KeyError, e:
803            return self.notFound
804        # Call the application-remembered handler but if there are any errors
805        # then log it and fallback to the standard message.
806        try:
807            return notFoundHandler.renderHTTP_notFound(PageContext(parent=ctx, tag=notFoundHandler))
808        except:
809            log.err()
810            return self.notFound
811
812    def __nonzero__(self):
813        return False
814
815
816# Not found singleton
817NotFound = None, ()
818