Commit 2ee03e77 authored by Jim Fulton's avatar Jim Fulton

Major update to use new state-management protocol.

parent bd514294
......@@ -9,12 +9,16 @@
# rights reserved.
#
############################################################################
__rcs_id__='$Id: TreeTag.py,v 1.12 1997/12/02 01:06:31 jim Exp $'
__version__='$Revision: 1.12 $'[11:-2]
__rcs_id__='$Id: TreeTag.py,v 1.13 1997/12/03 21:56:29 jim Exp $'
__version__='$Revision: 1.13 $'[11:-2]
from DocumentTemplate.DT_Util import *
from DocumentTemplate.DT_String import String
from string import join, split, rfind, find
from urllib import quote, unquote
from zlib import compress, decompress
from binascii import b2a_base64, a2b_base64
class Tree:
name='tree'
......@@ -57,124 +61,125 @@ class Tree:
String.commands['tree']=Tree
from string import join, split, rfind
from urllib import quote, unquote
pyid=id # Copy builtin
def tpRender(self, md, section, args):
"""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 converted to a
compressed and base64ed string that gets unencoded, uncompressed,
and evaluated 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 eval safe, we do not allow the character '*' in
the state.
"""
data=[]
if hasattr(self, 'tpId'): id=self.tpId()
elif hasattr(self, '_p_oid'): id=self._p_oid
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'] or md['state']
state=md['tree-state']
diff=md['tree-diff']
substate=md['-tree-substate-']
colspan=md['tree-colspan']
level=md['tree-level']
except KeyError:
# Check for collapse all, expand all, and state
try: collapse_all=md['collapse_all']
except: collapse_all=None
if collapse_all:
state=[]
else:
try: expand_all=md['expand_all']
except: expand_all=None
if expand_all:
state=tpValuesIds(self, args['branches'])
# OK, we are a top-level invocation
level=-1
if md.has_key('collapse_all'):
state=[id,[]],
elif md.has_key('expand_all'):
state=[id, tpValuesIds(self, args['branches'])],
else:
if md.has_key('tree-s'):
state=md['tree-s']
state=decode_seq(state)
try:
state=md['tree-state'] or md['state'] or md['-tree-state-']
if state[0] != '[': state=unquote(state)
state=list(eval(state,{'__builtins__':{}}))
except:
state=[]
colspan=1+tpStateLevel(state)
level = 0
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=rfind(root, '/')
l=rfind(root,'/')
if l >= 0: root=root[l+1:]
url=''
# Save state in a cookie
if state: md['RESPONSE'].setCookie('tree-state',quote(str(state)[1:-1]+','))
else: md['RESPONSE'].expireCookie('tree-state')
if substate==state: data.append('<TABLE CELLSPACING="0">\n')
#level=0
treeData={'tree-root-url': root,
'tree-colspan': colspan,
'tree-state': state }
md._push(treeData)
try:
for item in getattr(self, args['branches'])():
data=tpRenderTABLE(item,root,url,state,substate,data,colspan,
try: tpRenderTABLE(self,id,root,url,state,substate,diff,data,colspan,
section,md,treeData, level, args)
if state is substate: data.append('</TABLE>\n')
result=join(data,'')
finally: md._pop(1)
return result
def tpStateLevel(state, level=0):
for sub in state:
if len(sub)==2: level = max(level, 1+tpStateLevel(sub[1]))
else: level=max(level,1)
return level
def tpValuesIds(self, branches):
# This should build the ids of subitems which are
# expandable (non-empty). Leaves should never be
# in the state - it will screw the colspan counting.
r=[]
try:
try: items=getattr(self, branches)()
except AttributeError: items=()
for item in items:
try:
if getattr(item, branches)():
if state is substate:
state=state or ([id],)
state=encode_seq(state)
md['RESPONSE'].setCookie('tree-s',state)
if hasattr(item, 'tpId'): id=item.tpId()
elif hasattr(item, '_p_oid'): id=item._p_oid
else: id=pyid(item)
return join(data,'')
e=tpValuesIds(item, branches)
if e: id=[id,e]
else: id=[id]
r.append(id)
except: pass
except: pass
return r
def tpRenderTABLE(self, root_url, url, state, substate, data,
def tpRenderTABLE(self, id, root_url, url, state, substate, diff, data,
colspan, section, md, treeData, level=0, args=None):
"Render a tree as a table"
have_arg=args.has_key
try: items=getattr(self, args['branches'])()
except: items=None
if not items and have_arg('leaves'): items=1
tpUrl=self.tpURL()
url = (url and ('%s/%s' % (url, tpUrl))) or tpUrl
root_url = root_url or tpUrl
treeData['tree-item-url']=url
treeData['tree-level']=level
treeData['tree-item-expanded']=0
if hasattr(self, 'tpId'): id=self.tpId()
elif hasattr(self, '_p_oid'): id=self._p_oid
else: id=pyid(self)
exp=0
sub=None
output=data.append
try: items=getattr(self, args['branches'])()
except: items=None
if not items and have_arg('leaves'): items=1
diff.append(id)
if substate is state:
output('<TABLE CELLSPACING="0">\n')
sub=substate[0]
exp=items
else:
# Add prefix
output('<TR>\n')
......@@ -190,17 +195,25 @@ def tpRenderTABLE(self, root_url, url, state, substate, data,
if sub[0]==id:
exp=i+1
break
####################################
# Mostly inline encode_seq for speed
s=compress(str(diff))
if len(s) > 57: s=encode_str(s)
else:
s=b2a_base64(s)[:-1]
l=find(s,'=')
if l >= 0: s=s[:l]
####################################
if exp:
treeData['tree-item-expanded']=1
del substate[exp-1]
output('<A HREF="%s?tree-state=%s">%s</A>' %
(root_url,quote(str(state)[1:-1]+','), icoMinus))
substate.append(sub)
output('<A HREF="%s?tree-c=%s">%s</A>' %
(root_url,s, icoMinus))
else:
substate.append([id])
output('<A HREF="%s?tree-state=%s">%s</A>' %
(root_url,quote(str(state)[1:-1]+','), icoPlus))
del substate[-1]
output('<A HREF="%s?tree-e=%s">%s</A>' %
(root_url,s, icoPlus))
output('</TD>\n')
else:
if level > 2: output('<TD COLSPAN="%s"></TD>' % level)
......@@ -208,7 +221,6 @@ def tpRenderTABLE(self, root_url, url, state, substate, data,
output('<TD WIDTH="16"></TD>\n')
# add item text
dataspan=colspan-level
output('<TD%s%s VALIGN="TOP">' %
......@@ -263,12 +275,23 @@ def tpRenderTABLE(self, root_url, url, state, substate, data,
md._pop(1)
else:
__traceback_info__=sub, args, state, substate
ids={}
for item in items:
if hasattr(item, 'tpId'): id=item.tpId()
elif hasattr(item, '_p_oid'): id=item._p_oid
else: id=pyid(item)
if len(sub)==1: sub.append([])
data=tpRenderTABLE(item, root_url,url,state,sub[1],data,
substate=sub[1]
ids[id]=1
data=tpRenderTABLE(
item,id,root_url,url,state,substate,diff,data,
colspan, section, md, treeData, level, args)
if not sub[1]: del sub[1]
ids=ids.has_key
for i in range(len(substate)-1,-1):
if not ids(substate[i][0]): del substate[i]
if have_arg('footer'):
if md.has_key(args['footer']):
output(md.getitem(args['footer'],0)(
......@@ -282,9 +305,140 @@ def tpRenderTABLE(self, root_url, url, state, substate, data,
standard_html_footer='</TD></TR>',
))
del diff[-1]
if not diff: output('</TABLE>\n')
return data
def apply_diff(state, diff, expand):
if not diff: return
s=[None, state]
diff.reverse()
__traceback_info__=s, diff
while diff:
id=diff[-1]
del diff[-1]
if len(s)==1: s.append([])
s=s[1]
loc=-1
for i in range(len(s)):
if s[i][0]==id:
loc=i
break
if loc >= 0:
if not diff and not expand:
del s[loc]
else:
s=s[loc]
elif diff or expand:
s.append([id,[]])
s=s[-1][1]
while diff:
id=diff[-1]
del diff[-1]
if diff or expand:
s.append([id,[]])
s=s[-1][1]
def encode_seq(state):
"Convert a sequence to an encoded string"
state=compress(str(state))
l=len(state)
if l > 57:
states=[]
for i in range(0,l,57):
states.append(b2a_base64(state[i:i+57])[:-1])
state=join(states,'')
else: state=b2a_base64(state)[:-1]
l=find(state,'=')
if l >= 0: state=state[:l]
return state
def encode_str(state):
"Convert a sequence to an encoded string"
l=len(state)
if l > 57:
states=[]
for i in range(0,l,57):
states.append(b2a_base64(state[i:i+57])[:-1])
state=join(states,'')
else: state=b2a_base64(state)[:-1]
l=find(state,'=')
if l >= 0: state=state[:l]
return state
def decode_seq(state):
"Convert an encoded string to a sequence"
l=len(state)
if l > 76:
states=[]
j=0
for i in range(l/76):
k=j+76
states.append(a2b_base64(state[j:k]))
j=k
if j < l:
state=state[j:]
l=len(state)
k=l%4
if k: state=state+'='*(4-k)
states.append(a2b_base64(state))
state=join(states,'')
else:
l=len(state)
k=l%4
if k: state=state+'='*(4-k)
state=a2b_base64(state)
state=decompress(state)
if find(state,'*') >= 0: raise 'Illegal State', state
try: return list(eval(state,{'__builtins__':{}}))
except: return []
def tpStateLevel(state, level=0):
for sub in state:
if len(sub)==2: level = max(level, 1+tpStateLevel(sub[1]))
else: level=max(level,1)
return level
def tpValuesIds(self, branches):
# This should build the ids of subitems which are
# expandable (non-empty). Leaves should never be
# in the state - it will screw the colspan counting.
r=[]
try:
try: items=getattr(self, branches)()
except AttributeError: items=()
for item in items:
try:
if getattr(item, branches)():
if hasattr(item, 'tpId'): id=item.tpId()
elif hasattr(item, '_p_oid'): id=item._p_oid
else: id=pyid(item)
e=tpValuesIds(item, branches)
if e: id=[id,e]
else: id=[id]
r.append(id)
except: pass
except: pass
return r
icoSpace='<IMG SRC="%s/TreeDisplay/Blank_icon.gif" BORDER="0">' % SOFTWARE_URL
icoPlus ='<IMG SRC="%s/TreeDisplay/Plus_icon.gif" BORDER="0">' % SOFTWARE_URL
icoMinus='<IMG SRC="%s/TreeDisplay/Minus_icon.gif" BORDER="0">' % SOFTWARE_URL
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment