############################################################################## # # Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE # ############################################################################## """Rendering object hierarchies as Trees """ __rcs_id__='$Id: TreeTag.py 40218 2005-11-18 14:39:19Z andreasjung $' __version__='$Revision: 1.58 $'[11:-2] from DocumentTemplate.DT_Util import * from DocumentTemplate.DT_String import String from cPickle import dumps from string import translate from urllib import quote, unquote from zlib import compress, decompressobj from binascii import b2a_base64, a2b_base64 import re tbl=''.join(map(chr, range(256))) tplus=tbl[:ord('+')]+'-'+tbl[ord('+')+1:] tminus=tbl[:ord('-')]+'+'+tbl[ord('-')+1:] class Tree: name='tree' blockContinuations=() expand=None def __init__(self, blocks): tname, args, section = blocks[0] args=parse_params(args, name=None, expr=None, nowrap=1, expand=None, leaves=None, header=None, footer=None, branches=None, branches_expr=None, sort=None, reverse=1, skip_unauthorized=1, id=None, single=1, url=None, # opened_decoration=None, # closed_decoration=None, # childless_decoration=None, assume_children=1, urlparam=None, prefix=None) has_key=args.has_key if has_key('') or has_key('name') or has_key('expr'): name,expr=name_param(args,'tree',1) if expr is not None: args['expr']=expr elif has_key(''): args['name']=name else: name='a tree tag' if has_key('branches_expr'): if has_key('branches'): raise ParseError, _tm( 'branches and and branches_expr given', 'tree') args['branches_expr']=Eval(args['branches_expr']).eval elif not has_key('branches'): args['branches']='tpValues' if not has_key('id'): args['id']='tpId' if not has_key('url'): args['url']='tpURL' if not has_key('childless_decoration'): args['childless_decoration']='' prefix = args.get('prefix') if prefix and not simple_name(prefix): raise ParseError, _tm( 'prefix is not a simple name', 'tree') self.__name__ = name self.section=section.blocks self.args=args def render(self,md): args=self.args have=args.has_key if have('name'): v=md[args['name']] elif have('expr'): v=args['expr'].eval(md) else: v=md.this return tpRender(v,md,self.section, self.args) __call__=render String.commands['tree']=Tree pyid=id # Copy builtin simple_types = {str: 1, unicode: 1, int: 1, float: 1, long: 1, tuple: 1, list: 1, dict: 1} def try_call_attr(ob, attrname, simple_types=simple_types): attr = getattr(ob, attrname) if type(attr) in simple_types: return attr try: return attr() except TypeError: return attr def tpRender(self, md, section, args, try_call_attr=try_call_attr): """Render data organized as a tree. We keep track of open nodes using a cookie. The cookie stored the tree state. State should be a tree represented like: [] # all closed ['eagle'], # eagle is open ['eagle'], ['jeep', [1983, 1985]] # eagle, jeep, 1983 jeep and 1985 jeep where the items are object ids. The state will be pickled to a compressed and base64ed string that gets unencoded, uncompressed, and unpickled on the other side. Note that ids used in state need not be connected to urls, since state manipulation is internal to rendering logic. Note that to make unpickling safe, we use the MiniPickle module, that only creates safe objects """ data=[] idattr=args['id'] if hasattr(self, idattr): id = try_call_attr(self, idattr) elif hasattr(self, '_p_oid'): id=oid(self) else: id=pyid(self) try: # see if we are being run as a sub-document root=md['tree-root-url'] url=md['tree-item-url'] state=md['tree-state'] diff=md['tree-diff'] substate=md['-tree-substate-'] colspan=md['tree-colspan'] level=md['tree-level'] except KeyError: # OK, we are a top-level invocation level=-1 if md.has_key('collapse_all'): state=[id,[]], elif md.has_key('expand_all'): have_arg=args.has_key if have_arg('branches'): def get_items(node, branches=args['branches'], md=md): get = md.guarded_getattr if get is None: get = getattr items = get(node, branches) return items() elif have_arg('branches_expr'): def get_items(node, branches_expr=args['branches_expr'], md=md): md._push(InstanceDict(node, md)) items=branches_expr(md) md._pop() return items state=[id, tpValuesIds(self, get_items, args)], else: if md.has_key('tree-s'): state=md['tree-s'] state=decode_seq(state) try: if state[0][0] != id: state=[id,[]], except IndexError: state=[id,[]], else: state=[id,[]], if md.has_key('tree-e'): diff=decode_seq(md['tree-e']) apply_diff(state, diff, 1) if md.has_key('tree-c'): diff=decode_seq(md['tree-c']) apply_diff(state, diff, 0) colspan=tpStateLevel(state) substate=state diff=[] url='' root=md['URL'] l=root.rfind('/') if l >= 0: root=root[l+1:] treeData={'tree-root-url': root, 'tree-colspan': colspan, 'tree-state': state } prefix = args.get('prefix') if prefix: for k, v in treeData.items(): treeData[prefix + k[4:].replace('-', '_')] = v md._push(InstanceDict(self, md)) md._push(treeData) try: tpRenderTABLE(self,id,root,url,state,substate,diff,data,colspan, section,md,treeData, level, args) finally: md._pop(2) if state is substate and not (args.has_key('single') and args['single']): state=state or ([id],) state=encode_seq(state) md['RESPONSE'].setCookie('tree-s',state) return ''.join(data) def tpRenderTABLE(self, id, root_url, url, state, substate, diff, data, colspan, section, md, treeData, level=0, args=None, try_call_attr=try_call_attr, ): "Render a tree as a table" have_arg=args.has_key exp=0 if level >= 0: urlattr=args['url'] if urlattr and hasattr(self, urlattr): tpUrl = try_call_attr(self, urlattr) url = (url and ('%s/%s' % (url, tpUrl))) or tpUrl root_url = root_url or tpUrl ptreeData = add_with_prefix(treeData, 'tree', args.get('prefix')) ptreeData['tree-item-url']=url ptreeData['tree-level']=level ptreeData['tree-item-expanded']=0 idattr=args['id'] output=data.append items=None if (have_arg('assume_children') and args['assume_children'] and substate is not state): # We should not compute children unless we have to. # See if we've been asked to expand our children. for i in range(len(substate)): sub=substate[i] if sub[0]==id: exp=i+1 break if not exp: items=1 get=md.guarded_getattr if get is None: get = getattr if items is None: if have_arg('branches') and hasattr(self, args['branches']): items = get(self, args['branches']) items = items() elif have_arg('branches_expr'): items=args['branches_expr'](md) if not items and have_arg('leaves'): items=1 if items and items != 1: getitem = getattr(md, 'guarded_getitem', None) if getitem is not None: unauth=[] for index in range(len(items)): try: getitem(items, index) except ValidationError: unauth.append(index) if unauth: if have_arg('skip_unauthorized') and args['skip_unauthorized']: items=list(items) unauth.reverse() for index in unauth: del items[index] else: raise ValidationError, unauth if have_arg('sort'): # Faster/less mem in-place sort if type(items)==type(()): items=list(items) sort=args['sort'] size=range(len(items)) for i in size: v=items[i] k=getattr(v,sort) try: k=k() except: pass items[i]=(k,v) items.sort() for i in size: items[i]=items[i][1] if have_arg('reverse'): items=list(items) # Copy the list items.reverse() diff.append(id) _td_colspan='
')
for i in range(len(substate)):
sub=substate[i]
if sub[0]==id:
exp=i+1
break
####################################
# Mostly inline encode_seq for speed
s=compress(dumps(diff,1))
if len(s) > 57: s=encode_str(s)
else:
s=b2a_base64(s)[:-1]
l=s.find('=')
if l >= 0: s=s[:l]
s=translate(s, tplus)
####################################
script=md['BASEPATH1']
# Propagate extra args through tree.
if args.has_key( 'urlparam' ):
param = args['urlparam']
param = "%s&" % param
else:
param = ""
if exp:
ptreeData['tree-item-expanded']=1
output(''
' | \n')
else:
if level > 2: output(_td_colspan % level)
elif level > 0: output(_td_single * level)
output(_td_single)
output('\n')
# add item text
dataspan=colspan-level
output('' % ((dataspan > 1 and (' colspan="%s"' % dataspan) or ''), (have_arg('nowrap') and args['nowrap'] and ' style="white-space: nowrap"' or '')) ) output(render_blocks(section, md)) output(' | \n
' ' | ' % (h, (dataspan > 1 and (' colspan="%s"' % dataspan) or ''))), standard_html_footer=' |
' ' | ' % (h, (dataspan > 1 and (' colspan="%s"' % dataspan) or ''))), standard_html_footer=' |
' ' | ' % (h, (dataspan > 1 and (' colspan="%s"' % dataspan) or ''))), standard_html_footer=' |
' ' | ' % (h, (dataspan > 1 and (' colspan="%s"' % dataspan) or ''))), standard_html_footer=' |