[Subversion] / PEAK / src / peak / web / templates.py  

Diff of /PEAK/src/peak/web/templates.py

Parent Directory | Revision Log

version 1799, Thu Aug 19 21:25:11 2004 UTC version 2003, Thu Feb 3 16:19:55 2005 UTC
Line 2 
Line 2 
   
 TODO  TODO
   
  - implement interaction wrapper for "/skin", "/request", etc. data paths   - Address traversal nesting for referenced data
   
    - Dynamic attributes, independent of element?
   
    - Phase out old PWT syntax
   
  - implement sub-template support (convert doc->DOMlet in another doc)   - implement sub-template support (convert doc->DOMlet in another doc)
   
Line 20 
Line 24 
 from interfaces import *  from interfaces import *
 from xml.sax.saxutils import quoteattr, escape  from xml.sax.saxutils import quoteattr, escape
 from publish import TraversalPath  from publish import TraversalPath
 from environ import getAbsoluteURL, getInteraction, getCurrent  from peak.util import SOX, imports
 from environ import childContext, parentContext  from places import Decorator
 from peak.util import SOX  from environ import traverseItem, traverseDefault
   from errors import NotFound
   
 __all__ = [  __all__ = [
     'TEMPLATE_NS', 'DOMLETS_PROPERTY', 'TemplateDocument'      'TEMPLATE_NS', 'DOMLETS_PROPERTY', 'TemplateDocument'
Line 33 
Line 38 
   
 unicodeJoin = u''.join  unicodeJoin = u''.join
   
   
 def infiniter(sequence):  def infiniter(sequence):
     while 1:      while 1:
         for item in sequence:          for item in sequence:
             yield item              yield item
   
   
 class DOMletState(binding.Component):  class DOMletVars(Decorator):
   
     """Execution state for a DOMlet"""      state = None
   
     protocols.advise(  
         instancesProvide = [IDOMletState],  
     )  
   
     write = binding.Require("Unicode output stream write() method")      def traverseTo(self, name, ctx, default=NOT_GIVEN):
           loc = traverseItem(ctx, self.state, 'item', name, name, NOT_FOUND)
           if loc is not NOT_FOUND:
               return loc
   
           # attribute is absent or private, fall through to underlying object
           return traverseDefault(ctx, self.ob, 'attr', name, name, default)
   
     def findState(self, iface):  
   
         """Find nearest DOMletState implementing 'iface'"""  
   
         for c in binding.iterParents(self):     # XXX not covered by tests!  
             state = adapt(c,iface,None)  
             if state is not None:  
                 return state  
   
   class DOMletMethod(object):
       """Bind an 'IDOMletRenderable' to a specific context"""
   
       protocols.advise(
           instancesProvide = [IDOMletRenderable]
       )
   
       __slots__ = 'template','ctx'
   
       def __init__(self,ctx,template):
           self.ctx = ctx
           self.template = template
   
       def renderFor(self,ctx,state):
           return self.template.renderFor(self.ctx,state)
   
   
   
Line 70 
Line 80 
   
   
   
   class Parameters:
       """'params' object for templates"""
   
       protocols.advise( instancesProvide = [IWebTraversable] )
   
       def __init__(self,ctx,data):
           self.ctx = ctx
           self.data = data
           self.cache = {}
   
       def traverseTo(self, name, ctx, default=NOT_GIVEN):
           try:
               item = self.cache[name]
           except KeyError:
               try:
                   item = self.data[name]
               except KeyError:
                   if default is not NOT_GIVEN:
                       return default
                   raise NotFound(ctx,name,self)
               else:
                   tmpl = IDOMletRenderable(item,None)
                   if tmpl is not None:
                       item = self.cache[name] = DOMletMethod(self.ctx,tmpl)
                   else:
                       path = adapt(item,TraversalPath,None)
                       if path is not None:
                           self.data[name] = path
                           return path.traverse(self.ctx)
           return ctx.childContext(name,item)
   
   
       def beforeHTTP(self, ctx):
           return ctx
   
       def getURL(self,ctx):
           return ctx.traversedURL
   
   
   
   
 class DOMletAsHTTP(binding.Component):  class DOMletState(binding.Component):
   
     """Render a template component"""      """Execution state for a DOMlet"""
   
     protocols.advise(      protocols.advise(
         instancesProvide = [IHTTPHandler],          instancesProvide = [IDOMletState],
         asAdapterForProtocols = [IDOMletNode],  
         factoryMethod = 'fromNode'  
     )      )
   
     templateNode = binding.Require("""Node to render""")      write = binding.Require("Unicode output stream write() method")
   
     def fromNode(klass, subject):  
         return klass(templateNode = subject)  
   
     fromNode = classmethod(fromNode)      data = binding.Make(dict)
   
     def handle_http(self, environ, input, errors):      def __getitem__(self,key):
         myOwner = parentContext(environ)          return self.data[key]
   
         data = []      def withData(self,**kw):
           data = self.data.copy()
           data.update(kw)
           return self.__class__(self,data=data,write=self.write)
   
         self.templateNode.renderFor(      def wrapContext(self,ctx):
             myOwner,          return DOMletVars(ob=ctx, state=self)
             DOMletState(myOwner, write=data.append)  
         )  
   
         return '200 OK', [], [unicodeJoin(data)]    # XXX content-type      def findState(self, iface):
   
           """Find nearest DOMletState implementing 'iface'"""
   
           for c in binding.iterParents(self):     # XXX not covered by tests!
               state = adapt(c,iface,None)
               if state is not None:
                   return state
   
   
   
Line 120 
Line 162 
   
   
   
   def startElement(parser,data):
   
 class ElementAsBuilder(protocols.Adapter):      parent = data['previous']['pwt.content']
       factory = data.get('this.factory', parent.tagFactory)
   
     protocols.advise(      data['pwt.content'] = outer = factory(parent,
         instancesProvide = [SOX.IXMLBuilder],          tagName=data['name'],
         asAdapterForProtocols=[IDOMletElement]          attribItems=data['attributes'],
           domletProperty = data.get('this.domlet'),
           dataSpec  = data.get('this.data',''),
           paramName = data.get('this.is'),
       )
   
       inner = data.get('content.factory') or ('content.register' in data and parent.tagFactory)
       if inner:
           data['pwt.this'] = outer
           data['pwt.content'] = inner(outer,
               tagName='',
               attribItems=[],
               domletProperty = data.get('content.domlet'),
               dataSpec=data.get('content.data',''),
               paramName = data.get('content.is'),
     )      )
   
     def _xml_newTag(self, name,attrs,stack,parser):  
         self.nsUri = parser.nsInfo  
         myNs = self.myNs or ('',)   # use unprefixed NS if no NS defined  
         top = self.subject  
         factory = top.tagFactory  
         domletName = dataSpec = paramName = None  
         a = []; append = a.append  
   
         for k,v in attrs:  def finishElement(parser,data):
       content = data['pwt.content']
             if ':' in k:      for f in data.get('content.register',()):
                 ns, n = k.split(':',1)          f(content)
       if 'pwt.this' in data:
           this = data['pwt.this']
           this.addChild(content)
             else:              else:
                 ns, n = '', k          this = content
       for f in data.get('this.register',()):
           f(this)
       if 'previous' in data:
           data['previous']['pwt.content'].addChild(this)
       return this
   
   
             if n=='domlet' and ns in myNs:  def negotiateDomlet(parser, data, name, value):
                 # XXX if domletName is not None or dataSpec is not None:      data['attributes'].remove((name,value))
                 # XXX     raise ???      if ':' in value:
                 if ':' in v:          data['this.domlet'],data['this.data'] = value.split(':',1)
                     domletName, dataSpec = v.split(':',1)          domlet = data['this.domlet']
                 else:                  else:
                     domletName, dataSpec = v, ''          data['this.domlet'] = domlet = value
   
                 if domletName:      factory = DOMLETS_PROPERTY.of(data['previous']['pwt.content'])[domlet]
                     factory = DOMLETS_PROPERTY.of(top)[domletName]      if data.setdefault('this.factory',factory) is not factory:
                     factory = adapt(factory, IDOMletElementFactory)          parser.err('More than one "domlet" or "this:" replacement defined')
   
             elif n=='define' and ns in myNs:  
                 # XXX if paramName is not None:  
                 # XXX     raise ???  
                 paramName = v  
             else:  
                 append((k,v))  
   
         element = factory(top, tagName=name, attribItems=a,  def negotiateDefine(parser, data, name, value):
             domletProperty = domletName or None, dataSpec  = dataSpec or '',      data['attributes'].remove((name,value))
             paramName = paramName or None,      data['this.is'] = value
       parent = data['previous']['pwt.content']
       data.setdefault('this.register',[]).append(
           lambda ob: parent.addParameter(value,ob)
         )          )
   
         if paramName:  
             top.addParameter(paramName,element)  
   
         return element  def negotiatorFactory(domletFactory):
       def negotiate(mode, parser, data, name, value):
           data['attributes'].remove((name,value))
           factory = data.setdefault(mode+'.factory',domletFactory)
           if factory is not domletFactory:
               parser.err('More than one "domlet" or "this:" replacement defined')
           data[mode+'.data'] = value
           data[mode+'.domlet'] = parser.splitName(name)[1]
       return negotiate
   
   def nodeIs(mode, parser, data, name, value):
       data['attributes'].remove((name,value))
       data[mode+'.is'] = value
       data.setdefault(mode+'.register',[]).append(
           lambda ob: binding.getParentComponent(ob).addParameter(value,ob)
       )
   
   
   
   def setupElement(parser,data):
   
     def _xml_addChild(self,data):      d = dict(data.get('attributes',()))
         self.subject.addChild(data)  
   
     def _xml_finish(self):      if 'domlet' in d:
         return self.subject          negotiateDomlet(parser,data,'domlet',d['domlet'])
   
     def _xml_addText(self,xml):      if 'define' in d:
         top = self.subject          negotiateDefine(parser,data,'define',d['define'])
   
       def text(xml):
           top = data['pwt.content']
         top.addChild(top.textFactory(top,xml=escape(xml)))          top.addChild(top.textFactory(top,xml=escape(xml)))
   
     def _xml_addLiteral(self,xml):      def literal(xml):
         top = self.subject          top = data['pwt.content']
         top.addChild(top.literalFactory(top,xml=xml))          top.addChild(top.literalFactory(top,xml=xml))
   
       data['start'] = startElement
       data['finish'] = finishElement
       data['text'] = text
       data['literal'] = literal
   
     myNs = binding.Make(        # prefixes that currently map to TEMPLATE_NS  
         lambda self: dict(  
             [(p,1) for (p,u) in self.nsUri.items() if u and u[-1]==TEMPLATE_NS]  
         ),  
         attrName = 'myNs'  
     )  
   
   def setupDocument(parser,data):
       setupElement(parser,data)
       data['pwt.content'] = data['pwt_document']
   
   
   def withParam(parser,data,name,value):
       data['attributes'].remove((name,value))
       data.setdefault('content.register',[]).append(
           lambda ob: ob.addParameter(name.split(':',1)[-1],value)
       )
   
   
   
   
Line 260 
Line 342 
     domletProperty = None      domletProperty = None
     dataSpec       = binding.Make(lambda: '', adaptTo=TraversalPath)      dataSpec       = binding.Make(lambda: '', adaptTo=TraversalPath)
     paramName      = None      paramName      = None
     acceptParams   = binding.Obtain('domletProperty')      acceptParams   = ()
       multiParams    = ()
   
     # IDOMletNode      # IDOMletNode
   
Line 284 
Line 367 
   
   
   
   
     def optimizedChildren(self):      def optimizedChildren(self):
   
         """Child nodes with as many separate text nodes combined as possible"""          """Child nodes with as many separate text nodes combined as possible"""
Line 314 
Line 396 
   
   
     def _traverse(self, data, state):      def _traverse(self, data, state):
           return self.dataSpec.traverse(data,state.wrapContext), state
   
   
   
         return self.dataSpec.traverse(  
             data, lambda ctx: self._wrapInteraction(ctx)  
         ), state  
   
   
   
Line 358 
Line 440 
         self.children.append(node)          self.children.append(node)
   
   
   
   
   
   
   
   
   
   
   
     def addParameter(self, name, element):      def addParameter(self, name, element):
         """Declare 'element' as part of parameter 'name'"""          """Declare 'element' as part of parameter 'name'"""
         if self.acceptParams:  
             self.params.setdefault(name,[]).append(element)          if not self.acceptParams:
               return self.getParentComponent().addParameter(name,element)
   
           if name not in self.acceptParams and '*' not in self.acceptParams:
               # XXX need line info
               raise SyntaxError("Unrecognized parameter: %r" % name)
   
           is_multi = (
               name in self.multiParams or
               name not in self.acceptParams and '*' in self.multiParams
           )
   
           if name in self.params:
   
               if not is_multi:
                   raise SyntaxError(
                       "Multiple definitions for parameter: %r" % name
                   )   # XXX need line info
   
               self.params[name].append(element)
   
           elif is_multi:
               self.params[name] = [element]
   
         else:          else:
             self.getParentComponent().addParameter(name,element)              self.params[name] = element
   
   
   
   
   
   
   
     # Override in subclasses  
   
     def _wrapInteraction(self,interaction):  
         # XXX This should wrap the interaction in an IWebTraversable simulator,  
         # XXX which should include access to this element's parameters as well  
         # XXX as interaction variables.  
         raise NotImplementedError  
   
   
   
   
       # Override in subclasses
   
     _emptyTag = binding.Make(      _emptyTag = binding.Make(
         lambda self: self._openTag[:-1]+u' />'          lambda self: self.tagName and self._openTag[:-1]+u' />' or ''
     )      )
   
     _closeTag = binding.Make(      _closeTag = binding.Make(
         lambda self: u'</%s>' % self.tagName          lambda self: self.tagName and u'</%s>' % self.tagName or ''
     )      )
   
     _openTag = binding.Make(      _openTag = binding.Make(
         lambda self: u'<%s%s>' % ( self.tagName,          lambda self: self.tagName and u'<%s%s>' % ( self.tagName,
             unicodeJoin([              unicodeJoin([
                 u' %s=%s' % (k,quoteattr(v)) for (k,v) in self.attribItems                  u' %s=%s' % (k,quoteattr(v)) for (k,v) in self.attribItems
             ])              ])
           ) or ''
         )          )
     )  
   
     tagFactory     = None # real value is set below      tagFactory     = None # real value is set below
     textFactory    = Literal      textFactory    = Literal
Line 408 
Line 525 
   
   
   
   
   
   
   
   
   
 class TaglessElement(Element):  class TaglessElement(Element):
   
     """Element w/out tags"""      """Element w/out tags"""
Line 415 
Line 538 
     _openTag = _closeTag = _emptyTag = ''      _openTag = _closeTag = _emptyTag = ''
   
   
   class Uses(Element):
   
       """Render child elements with target data, or skip element altogether"""
   
       staticText = None
       render_if = True
   
       def renderFor(self, data, state):
           try:
               if self.dataSpec:
                   data, state = self._traverse(data, state)
           except (web.NotFound,web.NotAllowed):
               if self.render_if:
                   return
           else:
               if not self.render_if:
                   return
   
           state.write(self._openTag)
   
           for child in self.optimizedChildren:
               child.renderFor(data,state)
   
           state.write(self._closeTag)
   
   
   class Unless(Uses):
   
       """Skip child elements if target data is available"""
   
       render_if = False
   
   
   
 class TemplateDocument(TaglessElement):  class TemplateDocument(TaglessElement):
   
     """Document-level template element"""      """Document-level template element"""
   
     parserClass = SOX.ExpatBuilder      protocols.advise(
           instancesProvide = [IHTTPHandler],
           classProvides = [naming.IObjectFactory],
       )
   
       acceptParams = '*',     # handle any top-level parameters
   
     acceptParams = True     # handle any top-level parameters      def renderFor(self, ctx, state):
           if not self.fragment:
               raise TypeError("Can't be used as a fragment")
           return self.fragment.renderFor(ctx.parentContext(),state)
   
       def handle_http(self, ctx):
           name = ctx.shift()
           if name is not None:
               raise web.NotFound(ctx,name,self)   # No traversal to subobjects!
           if not self.page:
               raise web.UnsupportedMethod(ctx)    # We're not a page!
           data = []
           self.page.renderFor(
               ctx.parentContext(), DOMletState(self, write=data.append)
           )
           h = []
           if self.content_type:
               h.append(('Content-type',self.content_type))
           return '200 OK', h, [str(unicodeJoin(data))]    # XXX encoding
   
     def parseFile(self, stream):  
         self.parserClass().parseFile(stream,self)  
   
       def getObjectInstance(klass, context, refInfo, name, attrs=None):
           url, = refInfo.addresses
           return config.processXML(
               web.TEMPLATE_SCHEMA(context),str(url),pwt_document=klass(context),
           )
   
       getObjectInstance = classmethod(getObjectInstance)
   
   
       content_type = binding.Make(lambda self:
           str(self.params.get('content-type'))
       )
   
       def layoutDOMlet(self,d,attrName):
   
           if attrName+'-layout' in self.params:
               path = self.params[attrName+'-layout'] + ''  # ensure stringness
               if path=='/nothing':
                   return None
               elif path=='/default':
                   return super(TemplateDocument,self)
               else:
                   return Replace(self, dataSpec=path, params=self.params.copy())
   
           if attrName in self.params:
               return IDOMletRenderable(self.params[attrName])
   
           if attrName=='fragment':
               # It's okay to be a fragment by default
               return super(TemplateDocument,self)
   
       fragment = page = binding.Make(layoutDOMlet)
   
   
   
Line 449 
Line 651 
   
   
   
   
   
   
   class Replace(Element):
   
       staticText = None
       acceptParams = '*',
       escaped = True
   
       def renderFor(self,data,state):
   
           if self.dataSpec:
               ctx, state = self._traverse(data, state)
   
           if self.params:
               state = state.withData(params=Parameters(data,self.params))
   
           current = ctx.current
   
           domlet = IDOMletRenderable(current,None)
           if domlet is not None:
               return domlet.renderFor(ctx,state)
   
           # XXX dyn var comp goes here
           # XXX if NOT_FOUND -> return
           # XXX if NOT_GIVEN -> render original content
   
           current = unicode(current)
           if self.escaped:
               current = escape(current)
   
           state.write(current)
   
   
   class ReplaceXML(Replace):
       escaped = False
   
   
   
   
   
   
   
   
 class ContentReplacer(Element):  class ContentReplacer(Element):
   
     """Abstract base for elements that replace their contents"""      """Abstract base for elements that replace their contents"""
Line 471 
Line 717 
   
         write = state.write          write = state.write
         write(self._openTag)          write(self._openTag)
         write(escape(unicode(getCurrent(data))))          write(escape(unicode(data.current)))
         write(self._closeTag)          write(self._closeTag)
   
   
Line 485 
Line 731 
   
         write = state.write          write = state.write
         write(self._openTag)          write(self._openTag)
         write(unicode(getCurrent(data)))          write(unicode(data.current))
         write(self._closeTag)          write(self._closeTag)
   
   
Line 504 
Line 750 
     _openTag = _closeTag = _emptyTag = ''      _openTag = _closeTag = _emptyTag = ''
   
   
   class Expects(Element):
   
       """Render child elements with target data, or skip element altogether"""
   
       staticText = None
   
       dataSpec = ''   # to disable conversion to path
   
       protocol = binding.Make(
           lambda self: imports.importString(self.dataSpec),uponAssembly=True
       )
   
       def renderFor(self, data, state):
   
           data = data.clone(current=adapt(data.current,self.protocol))
   
           state.write(self._openTag)
           for child in self.optimizedChildren:
               child.renderFor(data,state)
           state.write(self._closeTag)
   
   
   
   
   
   
   
   
   
   
   
   
Line 542 
Line 788 
         if self.dataSpec:          if self.dataSpec:
             data, state = self._traverse(data, state)              data, state = self._traverse(data, state)
   
         url = unicode(getAbsoluteURL(data))          url = unicode(data.url)
   
         if not self.optimizedChildren and not self.nonEmpty:          if not self.optimizedChildren and not self.nonEmpty:
             state.write(self._emptyTag % locals())              state.write(self._emptyTag % locals())
Line 566 
Line 812 
         write = state.write          write = state.write
   
         write(self._openTag)          write(self._openTag)
         write(unicode(getAbsoluteURL(data)))          write(unicode(data.url))
         write(self._closeTag)          write(self._closeTag)
   
   class TaglessURLText(URLText):
       _openTag = _closeTag = _emptyTag = ''
   
 def URLTag(parentComponent, componentName=None, domletProperty=None, **kw):  def URLTag(parentComponent, componentName=None, domletProperty=None, **kw):
   
Line 615 
Line 861 
   
 class List(ContentReplacer):  class List(ContentReplacer):
   
       acceptParams = 'listItem','header','emptyList','footer'
       multiParams = 'listItem',
   
     def renderFor(self, data, state):      def renderFor(self, data, state):
   
         if self.dataSpec:          if self.dataSpec:
             data, state = self._traverse(data, state)              data, state = self._traverse(data, state)
   
         state.write(self._openTag)          state.write(self._openTag)
   
         nextPattern = infiniter(self.params['listItem']).next          nextPattern = infiniter(self.params['listItem']).next
         allowed     = getInteraction(data).allows          allowed     = data.allows
         ct = 0          ct = 0
   
         # XXX this should probably use an iteration location, or maybe          # XXX this should probably use an iteration location, or maybe
         # XXX put some properties in execution context for loop vars?          # XXX put some properties in execution context for loop vars?
   
         for item in getCurrent(data):          for item in data.current:
   
             if not allowed(item):              if not allowed(item):
                 continue                  continue
   
             if not ct:              if not ct:
                 for child in self.params.get('header',()):                  if 'header' in self.params:
                     child.renderFor(data,state)                      self.params['header'].renderFor(data,state)
   
             loc = childContext(data, str(ct), item)              loc = data.childContext(str(ct), item)
             nextPattern().renderFor(loc, state)              nextPattern().renderFor(loc, state)
             ct += 1              ct += 1
   
         if not ct:          if not ct:
             # Handle list being empty              # Handle list being empty
             for child in self.params.get('emptyList',()):              if 'emptyList' in self.params:
                 child.renderFor(data, state)                  self.params['emptyList'].renderFor(data,state)
         else:          else:
             for child in self.params.get('footer',()):              if 'footer' in self.params:
                 child.renderFor(data,state)                  self.params['footer'].renderFor(data,state)
   
         state.write(self._closeTag)          state.write(self._closeTag)
   
   class TaglessList(List):
       _openTag = _closeTag = _emptyTag = ''
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   
   


Generate output suitable for use with a patch program
Legend:
Removed from v.1799  
changed lines
  Added in v.2003

cvs-admin@eby-sarna.com

Powered by ViewCVS 1.0-dev

ViewCVS and CVS Help