|
|
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) |
|
|
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 peak.util import SOX, imports |
|
from places import Decorator |
|
from environ import traverseItem, traverseDefault |
|
from errors import NotFound |
|
|
__all__ = [ |
__all__ = [ |
'TEMPLATE_NS', 'DOMLETS_PROPERTY', 'DOMletParser', 'TemplateDocument' |
'TEMPLATE_NS', 'DOMLETS_PROPERTY', 'TemplateDocument' |
] |
] |
|
|
TEMPLATE_NS = 'http://peak.telecommunity.com/DOMlets/' |
TEMPLATE_NS = 'http://peak.telecommunity.com/DOMlets/' |
|
|
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 |
|
|
def isNull(ob): |
|
return ob is NOT_FOUND or ob is NOT_ALLOWED |
|
|
|
|
|
class DOMletState(binding.Component): |
|
|
|
"""Execution state for a DOMlet""" |
|
|
|
protocols.advise( |
|
instancesProvide = [IDOMletState], |
|
) |
|
|
|
interaction = binding.requireBinding("Current 'IWebInteraction'") |
|
|
|
write = binding.requireBinding("Unicode output stream write() method") |
|
|
|
|
|
def findState(self, iface): |
|
|
|
"""Find nearest DOMletState implementing 'iface'""" |
class DOMletVars(Decorator): |
|
|
for c in config.iterParents(self): |
|
state = adapt(c,iface,None) |
|
if state is not None: |
|
return state |
|
|
|
|
state = None |
|
|
|
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) |
|
|
|
|
|
class DOMletMethod(object): |
|
"""Bind an 'IDOMletRenderable' to a specific context""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DOMletAsWebPage(binding.Component): |
|
|
|
"""Render a template component""" |
|
|
|
protocols.advise( |
protocols.advise( |
instancesProvide = [IWebPage], |
instancesProvide = [IDOMletRenderable] |
asAdapterForProtocols = [IDOMletNode], |
|
factoryMethod = 'fromNode' |
|
) |
) |
|
|
templateNode = binding.requireBinding("""Node to render""") |
__slots__ = 'template','ctx' |
|
|
def fromNode(klass, subject, protocol): |
def __init__(self,ctx,template): |
return klass(templateNode = subject) |
self.ctx = ctx |
|
self.template = template |
fromNode = classmethod(fromNode) |
|
|
|
def render(self, interaction): |
|
myOwner = self.getParentComponent() |
|
data = [] |
|
self.templateNode.renderFor( |
|
myOwner, |
|
DOMletState( |
|
myOwner, interaction=interaction, write=data.append |
|
) |
|
) |
|
return unicodeJoin(data) |
|
|
|
|
def renderFor(self,ctx,state): |
|
return self.template.renderFor(self.ctx,state) |
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
class DOMletParser(binding.Component): |
def getURL(self,ctx): |
|
return ctx.traversedURL |
"""Parser that assembles a Document""" |
|
|
|
def parser(self,d,a): |
|
|
|
from xml.parsers.expat import ParserCreate |
|
p = ParserCreate() |
|
|
|
p.ordered_attributes = True |
|
p.returns_unicode = True |
|
p.specified_attributes = True |
|
|
|
p.StartDoctypeDeclHandler = self.startDoctype |
|
p.StartElementHandler = self.startElement |
|
p.EndElementHandler = self.endElement |
|
p.CharacterDataHandler = self.characters |
|
p.StartNamespaceDeclHandler = self.startNS |
|
p.EndNamespaceDeclHandler = self.endNS |
|
p.CommentHandler = self.comment |
|
|
|
# We don't use: |
|
# .StartNamespaceDeclHandler |
|
# .EndNamespaceDeclHandler |
|
# .XmlDeclHandler(version, encoding, standalone) |
|
# .ElementDeclHandler(name, model) |
|
# .AttlistDeclHandler(elname, attname, type, default, required) |
|
# .EndDoctypeDeclHandler() |
|
# .ProcessingInstructionHandler(target, data) |
|
# .UnparsedEntityDeclHandler(entityN,base,systemId,publicId,notationN) |
|
# .EntityDeclHandler( |
|
# entityName, is_parameter_entity, value, base, |
|
# systemId, publicId, notationName) |
|
# .NotationDeclHandler(notationName, base, systemId, publicId) |
|
# .StartCdataSectionHandler() |
|
# .EndCdataSectionHandler() |
|
# .NotStandaloneHandler() |
|
return p |
|
|
|
parser = binding.Once(parser) |
class DOMletState(binding.Component): |
|
|
domlets = binding.New(list) # "nearest explicit DOMlet" stack |
"""Execution state for a DOMlet""" |
stack = binding.New(list) # "DOMlet being assembled" stack |
|
nsUri = binding.New(dict) # URI stack for each NS prefix |
|
|
|
myNs = binding.Once( # prefixes that currently map to TEMPLATE_NS |
protocols.advise( |
lambda self,d,a: dict( |
instancesProvide = [IDOMletState], |
[(p,1) for (p,u) in self.nsUri.items() if u and u[-1]==TEMPLATE_NS] |
|
) |
|
) |
) |
|
|
|
write = binding.Require("Unicode output stream write() method") |
|
|
def parseFile(self, stream, document=None): |
data = binding.Make(dict) |
if document is None: |
|
document = TemplateDocument(self.getParentComponent()) |
|
self.stack.append(document) |
|
self.domlets.append(document) |
|
self.parser.ParseFile(stream) |
|
|
|
|
|
def comment(self,data): |
|
self.buildLiteral(u'<!--%s-->' % data) |
|
|
|
|
|
def startNS(self, prefix, uri): |
|
self.nsUri.setdefault(prefix,[]).append(uri) |
|
if uri==TEMPLATE_NS: |
|
self._delBinding('myNs') |
|
|
|
|
|
def endNS(self, prefix): |
|
uri = self.nsUri[prefix].pop() |
|
if uri==TEMPLATE_NS: |
|
self._delBinding('myNs') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
nsStack = binding.New(list) |
|
|
|
def pushNSinfo(self,attrs): |
|
|
|
prefixes = [] |
|
|
|
for i in range(0,len(attrs),2): |
|
|
|
k,v = attrs[i], attrs[i+1] |
|
|
|
if not k.startswith('xmlns'): |
|
continue |
|
|
|
rest = k[5:] |
|
if not rest: |
|
ns = '' |
|
elif rest.startswith(':'): |
|
ns = rest[1:] |
|
else: |
|
continue |
|
|
|
self.startNS(ns,v) |
|
prefixes.append(ns) |
|
|
|
self.nsStack.append(prefixes) |
|
|
|
|
|
def popNSinfo(self): |
|
map(self.endNS, self.nsStack.pop()) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def __getitem__(self,key): |
|
return self.data[key] |
|
|
|
def withData(self,**kw): |
|
data = self.data.copy() |
|
data.update(kw) |
|
return self.__class__(self,data=data,write=self.write) |
|
|
|
def wrapContext(self,ctx): |
|
return DOMletVars(ob=ctx, state=self) |
|
|
|
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 |
|
|
def startElement(self, name, attrs): |
|
|
|
self.pushNSinfo(attrs) |
|
|
|
a = [] |
|
append = a.append |
|
myNs = self.myNs or ('',) # use unprefixed NS if no NS defined |
|
|
|
top = self.stack[-1] |
|
factory = top.tagFactory |
|
domletName = dataSpec = paramName = None |
|
|
|
for i in range(0,len(attrs),2): |
|
|
|
k,v = attrs[i], attrs[i+1] |
|
|
|
if ':' in k: |
|
ns, n = k.split(':',1) |
|
else: |
|
ns, n = '', k |
|
|
|
if n=='domlet' and ns in myNs: |
|
# XXX if domletName is not None or dataSpec is not None: |
|
# XXX raise ??? |
|
if ':' in v: |
|
domletName, dataSpec = v.split(':',1) |
|
else: |
|
domletName, dataSpec = v, '' |
|
|
|
if domletName: |
|
factory = DOMLETS_PROPERTY.of(top)[domletName] |
|
factory = adapt(factory, IDOMletElementFactory) |
|
|
|
elif n=='define' and ns in myNs: |
def startElement(parser,data): |
# XXX if paramName is not None: |
|
# XXX raise ??? |
|
paramName = v |
|
else: |
|
append((k,v)) |
|
|
|
|
parent = data['previous']['pwt.content'] |
|
factory = data.get('this.factory', parent.tagFactory) |
|
|
element = factory(top, tagName=name, attribItems=a, |
data['pwt.content'] = outer = factory(parent, |
domletProperty = domletName or None, |
tagName=data['name'], |
dataSpec = dataSpec or '', |
attribItems=data['attributes'], |
paramName = paramName or None, |
domletProperty = data.get('this.domlet'), |
# XXX nonEmpty=False |
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'), |
) |
) |
|
|
if paramName: |
|
self.domlets[-1].addParameter(paramName,element) |
|
|
|
if domletName: |
def finishElement(parser,data): |
# New explicit DOMlet, put it on the explicit DOMlet stack |
content = data['pwt.content'] |
self.domlets.append(element) |
for f in data.get('content.register',()): |
|
f(content) |
|
if 'pwt.this' in data: |
|
this = data['pwt.this'] |
|
this.addChild(content) |
else: |
else: |
# Push the previous "nearest enclosing explicit DOMlet" |
this = content |
self.domlets.append(self.domlets[-1]) |
for f in data.get('this.register',()): |
|
f(this) |
self.stack.append(element) |
if 'previous' in data: |
|
data['previous']['pwt.content'].addChild(this) |
|
return this |
def endElement(self, name): |
|
self.domlets.pop() |
|
last = self.stack.pop() |
|
self.stack[-1].addChild(last) |
|
self.popNSinfo() |
|
|
|
|
|
def buildLiteral(self,xml): |
def negotiateDomlet(parser, data, name, value): |
top = self.stack[-1] |
data['attributes'].remove((name,value)) |
literal = top.literalFactory(top, xml=xml) |
if ':' in value: |
top.addChild(literal) |
data['this.domlet'],data['this.data'] = value.split(':',1) |
|
domlet = data['this.domlet'] |
|
|
def characters(self, data): |
|
top = self.stack[-1] |
|
text = top.textFactory(top, xml=escape(data)) |
|
top.addChild(text) |
|
|
|
|
|
|
|
|
|
def startDoctype(self, doctypeName, systemId, publicId, has_internal): |
|
|
|
if publicId: |
|
p = ' PUBLIC %s %s' % (quoteattr(publicId),quoteattr(systemId)) |
|
elif systemId: |
|
p = ' SYSTEM %s' % quoteattr(systemId) |
|
else: |
else: |
p = '' |
data['this.domlet'] = domlet = value |
|
|
# we ignore internal DTD subsets; they're not useful for HTML |
|
xml = u'<!DOCTYPE %s%s>\n' % (doctypeName, p) |
|
|
|
self.buildLiteral(xml) |
|
|
|
|
|
|
|
|
factory = DOMLETS_PROPERTY.of(data['previous']['pwt.content'])[domlet] |
|
if data.setdefault('this.factory',factory) is not factory: |
|
parser.err('More than one "domlet" or "this:" replacement defined') |
|
|
|
|
|
def negotiateDefine(parser, data, name, value): |
|
data['attributes'].remove((name,value)) |
|
data['this.is'] = value |
|
parent = data['previous']['pwt.content'] |
|
data.setdefault('this.register',[]).append( |
|
lambda ob: parent.addParameter(value,ob) |
|
) |
|
|
|
|
|
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): |
|
|
|
d = dict(data.get('attributes',())) |
|
|
|
if 'domlet' in d: |
|
negotiateDomlet(parser,data,'domlet',d['domlet']) |
|
|
|
if 'define' in d: |
|
negotiateDefine(parser,data,'define',d['define']) |
|
|
|
def text(xml): |
|
top = data['pwt.content'] |
|
top.addChild(top.textFactory(top,xml=escape(xml))) |
|
|
|
def literal(xml): |
|
top = data['pwt.content'] |
|
top.addChild(top.literalFactory(top,xml=xml)) |
|
|
|
data['start'] = startElement |
|
data['finish'] = finishElement |
|
data['text'] = text |
|
data['literal'] = literal |
|
|
|
|
|
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) |
|
) |
|
|
|
|
|
|
|
|
xml = u'' |
xml = u'' |
|
|
staticText = binding.bindTo('xml') |
staticText = binding.Obtain('xml') |
|
|
def renderFor(self, data, state): |
def renderFor(self, data, state): |
state.write(self.xml) |
state.write(self.xml) |
instancesProvide = [IDOMletElement], |
instancesProvide = [IDOMletElement], |
) |
) |
|
|
children = binding.New(list) |
children = binding.Make(list) |
params = binding.New(dict) |
params = binding.Make(dict) |
|
|
tagName = binding.requireBinding("Tag name of element") |
tagName = binding.Require("Tag name of element") |
attribItems = binding.requireBinding("Attribute name,value pairs") |
attribItems = binding.Require("Attribute name,value pairs") |
nonEmpty = False |
nonEmpty = False |
domletProperty = None |
domletProperty = None |
dataSpec = binding.Constant('', adaptTo=TraversalPath) |
dataSpec = binding.Make(lambda: '', adaptTo=TraversalPath) |
paramName = None |
paramName = None |
|
acceptParams = () |
|
multiParams = () |
|
|
# IDOMletNode |
# IDOMletNode |
|
|
def staticText(self, d, a): |
def staticText(self): |
|
|
"""Note: replace w/staticText = None in dynamic element subclasses""" |
"""Note: replace w/staticText = None in dynamic element subclasses""" |
|
|
else: |
else: |
return self._emptyTag |
return self._emptyTag |
|
|
staticText = binding.Once(staticText, suggestParent=False) |
staticText = binding.Make(staticText, suggestParent=False) |
|
|
|
|
|
|
|
def optimizedChildren(self): |
|
|
def optimizedChildren(self, d, a): |
|
|
|
"""Child nodes with as many separate text nodes combined as possible""" |
"""Child nodes with as many separate text nodes combined as possible""" |
|
|
flush() |
flush() |
return all |
return all |
|
|
optimizedChildren = binding.Once(optimizedChildren) |
optimizedChildren = binding.Make(optimizedChildren) |
|
|
|
|
def _traverse(self, data, state): |
def _traverse(self, data, state): |
|
return self.dataSpec.traverse(data,state.wrapContext), state |
|
|
|
|
|
|
|
|
|
|
if isNull(data): |
|
return data, state |
|
|
|
return self.dataSpec.traverse( |
|
data, state.interaction, lambda o,i: self._wrapInteraction(i) |
|
), state |
|
|
|
|
|
|
|
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'""" |
|
|
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 |
|
|
# Override in subclasses |
self.params[name].append(element) |
|
|
def _wrapInteraction(self,interaction): |
elif is_multi: |
# XXX This should wrap the interaction in an IWebTraversable simulator, |
self.params[name] = [element] |
# XXX which should include access to this element's parameters as well |
|
# XXX as interaction variables. |
|
raise NotImplementedError |
|
|
|
|
else: |
|
self.params[name] = element |
|
|
_emptyTag = binding.Once( |
|
lambda self,d,a: self._openTag[:-1]+u' />' |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Override in subclasses |
|
|
|
_emptyTag = binding.Make( |
|
lambda self: self.tagName and self._openTag[:-1]+u' />' or '' |
) |
) |
|
|
_closeTag = binding.Once( |
_closeTag = binding.Make( |
lambda self,d,a: u'</%s>' % self.tagName |
lambda self: self.tagName and u'</%s>' % self.tagName or '' |
) |
) |
|
|
_openTag = binding.Once( |
_openTag = binding.Make( |
lambda self,d,a: 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 |
literalFactory = Literal |
literalFactory = Literal |
|
|
|
|
|
|
class TemplateDocument(Element): |
|
|
|
"""Document-level template element""" |
|
|
|
|
|
|
|
|
|
|
class TaglessElement(Element): |
|
|
|
"""Element w/out tags""" |
|
|
_openTag = _closeTag = _emptyTag = '' |
_openTag = _closeTag = _emptyTag = '' |
|
|
parserClass = DOMletParser |
|
|
|
def parseFile(self, stream): |
class Uses(Element): |
parser = self.parserClass(self) |
|
parser.parseFile(stream,self) |
|
|
|
|
"""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): |
|
|
|
"""Document-level template element""" |
|
|
|
protocols.advise( |
|
instancesProvide = [IHTTPHandler], |
|
classProvides = [naming.IObjectFactory], |
|
) |
|
|
|
acceptParams = '*', # 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 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) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
"""Abstract base for elements that replace their contents""" |
"""Abstract base for elements that replace their contents""" |
|
|
staticText = None |
staticText = None |
children = optimizedChildren = binding.bindTo('contents') |
children = optimizedChildren = binding.Obtain('contents') |
contents = binding.requireBinding("nodes to render in element body") |
contents = binding.Require("nodes to render in element body") |
|
|
def addChild(self, node): |
def addChild(self, node): |
pass # ignore children, only parameters count with us |
pass # ignore children, only parameters count with us |
|
|
class Text(ContentReplacer): |
class Text(ContentReplacer): |
|
|
"""Replace element contents w/data""" |
"""Replace element contents w/data (XML-quoted)""" |
|
|
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) |
|
|
write = state.write |
write = state.write |
|
write(self._openTag) |
|
write(escape(unicode(data.current))) |
|
write(self._closeTag) |
|
|
|
|
|
class XML(ContentReplacer): |
|
|
|
"""Replace element contents w/data (XML structure)""" |
|
|
|
def renderFor(self, data, state): |
|
if self.dataSpec: |
|
data, state = self._traverse(data, state) |
|
|
|
write = state.write |
write(self._openTag) |
write(self._openTag) |
|
write(unicode(data.current)) |
|
write(self._closeTag) |
|
|
|
|
|
|
|
class TaglessText(Text): |
|
|
|
"""Text w/out open/close tag""" |
|
|
|
_openTag = _closeTag = _emptyTag = '' |
|
|
|
|
|
class TaglessXML(XML): |
|
|
|
"""XML w/out open/close tag""" |
|
|
|
_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): |
|
|
if not isNull(data): |
data = data.clone(current=adapt(data.current,self.protocol)) |
write(unicode(data.getObject(state.interaction))) |
|
|
state.write(self._openTag) |
|
for child in self.optimizedChildren: |
|
child.renderFor(data,state) |
|
state.write(self._closeTag) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class URLAttribute(Element): |
|
|
|
"""Put the URL in an attribute""" |
|
|
|
staticText = None |
|
|
|
def renderFor(self, data, state): |
|
|
|
if self.dataSpec: |
|
data, state = self._traverse(data, state) |
|
|
|
url = unicode(data.url) |
|
|
|
if not self.optimizedChildren and not self.nonEmpty: |
|
state.write(self._emptyTag % locals()) |
|
return |
|
|
|
state.write(self._openTag % locals()) |
|
for child in self.optimizedChildren: |
|
child.renderFor(data,state) |
|
state.write(self._closeTag) |
|
|
|
|
|
class URLText(ContentReplacer): |
|
|
|
"""Write absolute URL as body text""" |
|
|
|
def renderFor(self, data, state): |
|
|
|
if self.dataSpec: |
|
data, state = self._traverse(data, state) |
|
|
|
write = state.write |
|
|
|
write(self._openTag) |
|
write(unicode(data.url)) |
write(self._closeTag) |
write(self._closeTag) |
|
|
|
class TaglessURLText(URLText): |
|
_openTag = _closeTag = _emptyTag = '' |
|
|
|
def URLTag(parentComponent, componentName=None, domletProperty=None, **kw): |
|
|
|
"""Create a URLText or URLAttribute DOMlet based on parameters""" |
|
|
|
kw['domletProperty'] = domletProperty |
|
prop = (domletProperty or '').split('.') |
|
|
|
if len(prop)==1 or prop[-1]=='text': |
|
return URLText(parentComponent, componentName, **kw) |
|
|
|
elif prop[-1]=='notag': |
|
kw['_openTag'] = kw['_closeTag'] = '' |
|
return URLText(parentComponent, componentName, **kw) |
|
|
|
else: |
|
attrName = prop[-1].replace('+',':') |
|
attrs = [(k,v.replace('%','%%')) for (k,v) in kw.get('attribItems',())] |
|
d = dict(attrs) |
|
|
|
if attrName not in d: |
|
attrs.append((attrName,'%(url)s')) |
|
else: |
|
attrs = [ |
|
tuple([k]+((k!=attrName) and [v] or ['%(url)s'])) |
|
for (k,v) in attrs |
|
] |
|
|
|
kw['attribItems'] = attrs |
|
return URLAttribute(parentComponent, componentName, **kw) |
|
|
|
protocols.adviseObject(URLTag, provides=[IDOMletElementFactory]) |
|
|
|
|
|
|
|
|
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 |
i = infiniter(self.params['listItem']) |
allowed = data.allows |
interaction = state.interaction |
|
pathProtocol = interaction.pathProtocol |
|
suggest = binding.suggestParentComponent |
|
ct = 0 |
ct = 0 |
|
|
if not isNull(data): |
# XXX this should probably use an iteration location, or maybe |
|
# XXX put some properties in execution context for loop vars? |
|
|
for item in data.getObject(interaction): |
for item in data.current: |
if not interaction.allows(item): |
|
continue |
|
|
|
loc = adapt(item, pathProtocol, None) |
if not allowed(item): |
if loc is None: |
|
continue |
continue |
|
|
# XXX this should probably use an iteration location, or maybe |
if not ct: |
# XXX put some properties in execution context for loop vars? |
if 'header' in self.params: |
suggest(data,None,loc) # XXX use numeric name? |
self.params['header'].renderFor(data,state) |
i.next().renderFor(loc, state) |
|
|
loc = data.childContext(str(ct), item) |
|
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: |
|
if 'footer' in self.params: |
|
self.params['footer'].renderFor(data,state) |
|
|
state.write(self._closeTag) |
state.write(self._closeTag) |
|
|
|
class TaglessList(List): |
|
_openTag = _closeTag = _emptyTag = '' |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|