summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: 0fbc97d)
raw | patch | inline | side by side (parent: 0fbc97d)
author | richard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2> | |
Fri, 30 Aug 2002 08:25:34 +0000 (08:25 +0000) | ||
committer | richard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2> | |
Fri, 30 Aug 2002 08:25:34 +0000 (08:25 +0000) |
git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@1000 57a73879-2fb5-44c3-a270-3262357dd7e2
ZTUtils/Batch.py | [new file with mode: 0644] | patch | blob |
ZTUtils/CHANGES.txt | [new file with mode: 0644] | patch | blob |
ZTUtils/HISTORY.txt | [new file with mode: 0644] | patch | blob |
ZTUtils/Iterator.py | [new file with mode: 0644] | patch | blob |
ZTUtils/SimpleTree.py | [new file with mode: 0644] | patch | blob |
ZTUtils/Tree.py | [new file with mode: 0644] | patch | blob |
ZTUtils/Zope.py | [new file with mode: 0644] | patch | blob |
ZTUtils/__init__.py | [new file with mode: 0644] | patch | blob |
diff --git a/ZTUtils/Batch.py b/ZTUtils/Batch.py
--- /dev/null
+++ b/ZTUtils/Batch.py
@@ -0,0 +1,121 @@
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (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
+#
+##############################################################################
+__doc__='''Batch class, for iterating over a sequence in batches
+
+$Id: Batch.py,v 1.1 2002-08-30 08:25:33 richard Exp $'''
+__version__='$Revision: 1.1 $'[11:-2]
+
+from ExtensionClass import Base
+
+class LazyPrevBatch(Base):
+ def __of__(self, parent):
+ return Batch(parent._sequence, parent._size,
+ parent.first - parent._size + parent.overlap, 0,
+ parent.orphan, parent.overlap)
+
+class LazyNextBatch(Base):
+ def __of__(self, parent):
+ try: parent._sequence[parent.end]
+ except IndexError: return None
+ return Batch(parent._sequence, parent._size,
+ parent.end - parent.overlap, 0,
+ parent.orphan, parent.overlap)
+
+class LazySequenceLength(Base):
+ def __of__(self, parent):
+ parent.sequence_length = l = len(parent._sequence)
+ return l
+
+class Batch(Base):
+ """Create a sequence batch"""
+ __allow_access_to_unprotected_subobjects__ = 1
+
+ previous = LazyPrevBatch()
+ next = LazyNextBatch()
+ sequence_length = LazySequenceLength()
+
+ def __init__(self, sequence, size, start=0, end=0,
+ orphan=0, overlap=0):
+ '''Encapsulate "sequence" in batches of "size".
+
+ Arguments: "start" and "end" are 0-based indexes into the
+ sequence. If the next batch would contain no more than
+ "orphan" elements, it is combined with the current batch.
+ "overlap" is the number of elements shared by adjacent
+ batches. If "size" is not specified, it is computed from
+ "start" and "end". Failing that, it is 7.
+
+ Attributes: Note that the "start" attribute, unlike the
+ argument, is a 1-based index (I know, lame). "first" is the
+ 0-based index. "length" is the actual number of elements in
+ the batch.
+
+ "sequence_length" is the length of the original, unbatched, sequence
+ '''
+
+ start = start + 1
+
+ start,end,sz = opt(start,end,size,orphan,sequence)
+
+ self._sequence = sequence
+ self.size = sz
+ self._size = size
+ self.start = start
+ self.end = end
+ self.orphan = orphan
+ self.overlap = overlap
+ self.first = max(start - 1, 0)
+ self.length = self.end - self.first
+ if self.first == 0:
+ self.previous = None
+
+
+ def __getitem__(self, index):
+ if index < 0:
+ if index + self.end < self.first: raise IndexError, index
+ return self._sequence[index + self.end]
+
+ if index >= self.length: raise IndexError, index
+ return self._sequence[index+self.first]
+
+ def __len__(self):
+ return self.length
+
+def opt(start,end,size,orphan,sequence):
+ if size < 1:
+ if start > 0 and end > 0 and end >= start:
+ size=end+1-start
+ else: size=7
+
+ if start > 0:
+
+ try: sequence[start-1]
+ except IndexError: start=len(sequence)
+
+ if end > 0:
+ if end < start: end=start
+ else:
+ end=start+size-1
+ try: sequence[end+orphan-1]
+ except IndexError: end=len(sequence)
+ elif end > 0:
+ try: sequence[end-1]
+ except IndexError: end=len(sequence)
+ start=end+1-size
+ if start - 1 < orphan: start=1
+ else:
+ start=1
+ end=start+size-1
+ try: sequence[end+orphan-1]
+ except IndexError: end=len(sequence)
+ return start,end,size
diff --git a/ZTUtils/CHANGES.txt b/ZTUtils/CHANGES.txt
--- /dev/null
+++ b/ZTUtils/CHANGES.txt
@@ -0,0 +1,28 @@
+ZTUtils changes
+
+ This file contains change information for the current release.
+ Change information for previous versions can be found in the
+ file HISTORY.txt.
+
+ Version 1.5
+
+ Features Added
+
+ - Added 'sequence_length' attribute to batches.
+
+ - Under Python 2.2, Iterator both accepts and produces Python
+ iterator objects.
+
+ - first() and last() methods allow you to tell whether the
+ current element is different from the next or previous
+ element. This is most useful when the sequence is sorted.
+
+ Bugs Fixed
+
+ - Handle both string and class Unauthorized exceptions.
+
+ - Batch construction masked sequence errors, such as
+ Unauthorized.
+
+ - Orphan defaulted to different values in different places.
+
diff --git a/ZTUtils/HISTORY.txt b/ZTUtils/HISTORY.txt
--- /dev/null
+++ b/ZTUtils/HISTORY.txt
@@ -0,0 +1,57 @@
+ZTUtils history
+
+ This file contains change information for previous versions of
+ ZTUtils. Change information for the current release can be found
+ in the file CHANGES.txt.
+
+
+ Version 1.1.3
+
+ Brown-bag bugfix release.
+
+ Version 1.1.2
+
+ Bugs Fixed
+
+ - Orphans defaulted to 3, which was confusing and out of sync
+ with DTML-In.
+
+ - Orphan batches were broken.
+
+ Version 1.1.1
+
+ Bugs Fixed
+
+ - Python 1.5.2-incompatible changes crept in.
+
+ Version 1.1.0
+
+ Features Added
+
+ - TreeMakers have a setChildAccess() method that you can use
+ to control tree construction. Child nodes can be accessed
+ through either an attribute name or callback function.
+ Children fetched by attribute name can be filtered through a
+ callback function.
+
+ - A new LazyFilter class allows you to filter a sequence using
+ Zope security and an optional filter callback function. The
+ security and filter tests are lazy, meaning they are
+ performed as late as possible.
+
+ The optional 'skip' argument determines the reaction when
+ access to a sequence element is refused by the Zope security
+ policy. The default (None) is to raise the 'Unauthorized'
+ exception. If a string is passed, such elements are
+ skipped. If the string is non-empty, it is treated as a
+ permission name, and the element is skipped if the user
+ doesn't have that permission on the element.
+
+ - The Zope versions of TreeMaker, SimpleTreeMaker, and Batch
+ now use LazyFilter. The TreeMakers have a setSkip() method
+ that can be used to set the 'skip' value. Batch has an
+ optional 'skip_unauthorized' argument that is passed to
+ LazyFilter as 'skip'.
+
+ - Utility functions make_query(), url_query(), and
+ make_hidden_input() have been added.
diff --git a/ZTUtils/Iterator.py b/ZTUtils/Iterator.py
--- /dev/null
+++ b/ZTUtils/Iterator.py
@@ -0,0 +1,198 @@
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (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
+#
+##############################################################################
+__doc__='''Iterator class
+
+Unlike the builtin iterators of Python 2.2+, these classes are
+designed to maintain information about the state of an iteration.
+The Iterator() function accepts either a sequence or a Python
+iterator. The next() method fetches the next item, and returns
+true if it succeeds.
+
+$Id: Iterator.py,v 1.1 2002-08-30 08:25:34 richard Exp $'''
+__version__='$Revision: 1.1 $'[11:-2]
+
+import string
+
+class Iterator:
+ '''Simple Iterator class'''
+
+ __allow_access_to_unprotected_subobjects__ = 1
+
+ nextIndex = 0
+ def __init__(self, seq):
+ self.seq = seq
+ for inner in seqInner, iterInner:
+ if inner._supports(seq):
+ self._inner = inner
+ self._prep_next = inner.prep_next
+ return
+ raise TypeError, "Iterator does not support %s" % `seq`
+
+ def __getattr__(self, name):
+ try:
+ inner = getattr(self._inner, 'it_' + name)
+ except AttributeError:
+ raise AttributeError, name
+ return inner(self)
+
+ def next(self):
+ if not (hasattr(self, '_next') or self._prep_next(self)):
+ return 0
+ self.index = i = self.nextIndex
+ self.nextIndex = i+1
+ self._advance(self)
+ return 1
+
+ def _advance(self, it):
+ self.item = self._next
+ del self._next
+ del self.end
+ self._advance = self._inner.advance
+ self.start = 1
+
+ def number(self): return self.nextIndex
+
+ def even(self): return not self.index % 2
+
+ def odd(self): return self.index % 2
+
+ def letter(self, base=ord('a'), radix=26):
+ index = self.index
+ s = ''
+ while 1:
+ index, off = divmod(index, radix)
+ s = chr(base + off) + s
+ if not index: return s
+
+ def Letter(self):
+ return self.letter(base=ord('A'))
+
+ def Roman(self, rnvalues=(
+ (1000,'M'),(900,'CM'),(500,'D'),(400,'CD'),
+ (100,'C'),(90,'XC'),(50,'L'),(40,'XL'),
+ (10,'X'),(9,'IX'),(5,'V'),(4,'IV'),(1,'I')) ):
+ n = self.index + 1
+ s = ''
+ for v, r in rnvalues:
+ rct, n = divmod(n, v)
+ s = s + r * rct
+ return s
+
+ def roman(self, lower=string.lower):
+ return lower(self.Roman())
+
+ def first(self, name=None):
+ if self.start: return 1
+ return not self.same_part(name, self._last, self.item)
+
+ def last(self, name=None):
+ if self.end: return 1
+ return not self.same_part(name, self.item, self._next)
+
+ def same_part(self, name, ob1, ob2):
+ if name is None:
+ return ob1 == ob2
+ no = []
+ return getattr(ob1, name, no) == getattr(ob2, name, no) is not no
+
+ def __iter__(self):
+ return IterIter(self)
+
+class InnerBase:
+ '''Base Inner class for Iterators'''
+ # Prep sets up ._next and .end
+ def prep_next(self, it):
+ it.next = self.no_next
+ it.end = 1
+ return 0
+
+ # Advance knocks them down
+ def advance(self, it):
+ it._last = it.item
+ it.item = it._next
+ del it._next
+ del it.end
+ it.start = 0
+
+ def no_next(self, it):
+ return 0
+
+ def it_end(self, it):
+ if hasattr(it, '_next'):
+ return 0
+ return not self.prep_next(it)
+
+class SeqInner(InnerBase):
+ '''Inner class for sequence Iterators'''
+
+ def _supports(self, ob):
+ try: ob[0]
+ except TypeError: return 0
+ except: pass
+ return 1
+
+ def prep_next(self, it):
+ i = it.nextIndex
+ try:
+ it._next = it.seq[i]
+ except IndexError:
+ it._prep_next = self.no_next
+ it.end = 1
+ return 0
+ it.end = 0
+ return 1
+
+ def it_length(self, it):
+ it.length = l = len(it.seq)
+ return l
+
+try:
+ StopIteration=StopIteration
+except NameError:
+ StopIteration="StopIteration"
+
+class IterInner(InnerBase):
+ '''Iterator inner class for Python iterators'''
+
+ def _supports(self, ob):
+ try:
+ if hasattr(ob, 'next') and (ob is iter(ob)):
+ return 1
+ except:
+ return 0
+
+ def prep_next(self, it):
+ try:
+ it._next = it.seq.next()
+ except StopIteration:
+ it._prep_next = self.no_next
+ it.end = 1
+ return 0
+ it.end = 0
+ return 1
+
+class IterIter:
+ def __init__(self, it):
+ self.it = it
+ self.skip = it.nextIndex > 0 and not it.end
+ def next(self):
+ it = self.it
+ if self.skip:
+ self.skip = 0
+ return it.item
+ if it.next():
+ return it.item
+ raise StopIteration
+
+seqInner = SeqInner()
+iterInner = IterInner()
diff --git a/ZTUtils/SimpleTree.py b/ZTUtils/SimpleTree.py
--- /dev/null
+++ b/ZTUtils/SimpleTree.py
@@ -0,0 +1,57 @@
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (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
+#
+##############################################################################
+__doc__='''Simple Tree classes
+
+$Id: SimpleTree.py,v 1.1 2002-08-30 08:25:34 richard Exp $'''
+__version__='$Revision: 1.1 $'[11:-2]
+
+from Tree import TreeMaker, TreeNode, b2a
+
+class SimpleTreeNode(TreeNode):
+ def branch(self):
+ if self.state == 0:
+ return {'link': None, 'img': ' '}
+
+ if self.state < 0:
+ setst = 'expand'
+ exnum = self.aq_parent.expansion_number
+ img = 'pl'
+ else:
+ setst = 'collapse'
+ exnum = self.expansion_number
+ img = 'mi'
+
+ base = self.aq_acquire('baseURL')
+ obid = self.id
+ pre = self.aq_acquire('tree_pre')
+
+ return {'link': '?%s-setstate=%s,%s,%s#%s' % (pre, setst[0],
+ exnum, obid, obid),
+ 'img': '<img src="%s/p_/%s" alt="%s" border="0">' % (base, img, setst)}
+
+
+class SimpleTreeMaker(TreeMaker):
+ '''Generate Simple Trees'''
+
+ def __init__(self, tree_pre="tree"):
+ self.tree_pre = tree_pre
+
+ def node(self, object):
+ node = SimpleTreeNode()
+ node.object = object
+ node.id = b2a(self.getId(object))
+ return node
+
+ def markRoot(self, node):
+ node.tree_pre = self.tree_pre
+ node.baseURL = node.object.REQUEST['BASEPATH1']
diff --git a/ZTUtils/Tree.py b/ZTUtils/Tree.py
--- /dev/null
+++ b/ZTUtils/Tree.py
@@ -0,0 +1,240 @@
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (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
+#
+##############################################################################
+__doc__='''Tree manipulation classes
+
+$Id: Tree.py,v 1.1 2002-08-30 08:25:34 richard Exp $'''
+__version__='$Revision: 1.1 $'[11:-2]
+
+from Acquisition import Explicit
+from ComputedAttribute import ComputedAttribute
+
+class TreeNode(Explicit):
+ __allow_access_to_unprotected_subobjects__ = 1
+ state = 0 # leaf
+ height = 1
+ size = 1
+ def __init__(self):
+ self._child_list = []
+ def _add_child(self, child):
+ 'Add a child which already has all of its children.'
+ self._child_list.append(child)
+ self.height = max(self.height, child.height + 1)
+ self.size = self.size + child.size
+ def flat(self):
+ 'Return a flattened preorder list of tree nodes'
+ items = []
+ self.walk(items.append)
+ return items
+ def walk(self, f, data=None):
+ 'Preorder walk this tree, passing each node to a function'
+ if data is None:
+ f(self)
+ else:
+ f(self, data)
+ for child in self._child_list:
+ child.__of__(self).walk(f, data)
+ def _depth(self):
+ return self.aq_parent.depth + 1
+ depth = ComputedAttribute(_depth, 1)
+ def __getitem__(self, index):
+ return self._child_list[index].__of__(self)
+ def __len__(self):
+ return len(self._child_list)
+
+_marker = []
+
+class TreeMaker:
+ '''Class for mapping a hierachy of objects into a tree of nodes.'''
+
+ __allow_access_to_unprotected_subobjects__ = 1
+
+ _id = 'tpId'
+ _values = 'tpValues'
+ _assume_children = 0
+ _values_filter = None
+ _values_function = None
+ _expand_root = 1
+
+ def setChildAccess(self, attrname=_marker, filter=_marker,
+ function=_marker):
+ '''Set the criteria for fetching child nodes.
+
+ Child nodes can be accessed through either an attribute name
+ or callback function. Children fetched by attribute name can
+ be filtered through a callback function.
+ '''
+ if function is _marker:
+ self._values_function = None
+ if attrname is not _marker:
+ self._values = str(attrname)
+ if filter is not _marker:
+ self._values_filter = filter
+ else:
+ self._values_function = function
+
+ def tree(self, root, expanded=None, subtree=0):
+ '''Create a tree from root, with specified nodes expanded.
+
+ "expanded" must be false, true, or a mapping.
+ Each key of the mapping is the id of a top-level expanded
+ node, and each value is the "expanded" value for the
+ children of that node.
+ '''
+ node = self.node(root)
+ child_exp = expanded
+ if not simple_type(expanded):
+ # Assume a mapping
+ expanded = expanded.has_key(node.id)
+ child_exp = child_exp.get(node.id)
+ if expanded or (not subtree and self._expand_root):
+ children = self.getChildren(root)
+ if children:
+ node.state = 1 # expanded
+ for child in children:
+ node._add_child(self.tree(child, child_exp, 1))
+ elif self.hasChildren(root):
+ node.state = -1 # collapsed
+ if not subtree:
+ node.depth = 0
+ if hasattr(self, 'markRoot'):
+ self.markRoot(node)
+ return node
+
+ def node(self, object):
+ node = TreeNode()
+ node.object = object
+ node.id = b2a(self.getId(object))
+ return node
+
+ def getId(self, object):
+ id_attr = self._id
+ if hasattr(object, id_attr):
+ obid = getattr(object, id_attr)
+ if not simple_type(obid): obid = obid()
+ return obid
+ if hasattr(object, '_p_oid'): return str(object._p_oid)
+ return id(object)
+
+ def hasChildren(self, object):
+ if self._assume_children:
+ return 1
+ return self.getChildren(object)
+
+ def getChildren(self, object):
+ if self._values_function is not None:
+ return self._values_function(object)
+ if self._values_filter and hasattr(object, 'aq_acquire'):
+ return object.aq_acquire(self._values, aqcallback,
+ self._values_filter)()
+ return getattr(object, self._values)()
+
+def simple_type(ob,
+ is_simple={type(''):1, type(0):1, type(0.0):1,
+ type(0L):1, type(None):1 }.has_key):
+ return is_simple(type(ob))
+
+def aqcallback(self, inst, parent, name, value, filter):
+ return filter(self, inst, parent, name, value)
+
+from binascii import b2a_base64, a2b_base64
+import string
+from string import split, join, translate
+
+a2u_map = string.maketrans('+/=', '-._')
+u2a_map = string.maketrans('-._', '+/=')
+
+def b2a(s):
+ '''Encode a value as a cookie- and url-safe string.
+
+ Encoded string use only alpahnumeric characters, and "._-".
+ '''
+ s = str(s)
+ if len(s) <= 57:
+ return translate(b2a_base64(s)[:-1], a2u_map)
+ frags = []
+ for i in range(0, len(s), 57):
+ frags.append(b2a_base64(s[i:i + 57])[:-1])
+ return translate(join(frags, ''), a2u_map)
+
+def a2b(s):
+ '''Decode a b2a-encoded string.'''
+ s = translate(s, u2a_map)
+ if len(s) <= 76:
+ return a2b_base64(s)
+ frags = []
+ for i in range(0, len(s), 76):
+ frags.append(a2b_base64(s[i:i + 76]))
+ return join(frags, '')
+
+def encodeExpansion(nodes):
+ '''Encode the expanded node ids of a tree into a string.
+
+ Accepts a list of nodes, such as that produced by root.flat().
+ Marks each expanded node with an expansion_number attribute.
+ Since node ids are encoded, the resulting string is safe for
+ use in cookies and URLs.
+ '''
+ steps = []
+ last_depth = -1
+ n = 0
+ for node in nodes:
+ if node.state <=0: continue
+ dd = last_depth - node.depth + 1
+ last_depth = node.depth
+ if dd > 0:
+ steps.append('.' * dd)
+ steps.append(node.id)
+ node.expansion_number = n
+ n = n + 1
+ return join(steps, ':')
+
+def decodeExpansion(s, nth=None):
+ '''Decode an expanded node map from a string.
+
+ If nth is an integer, also return the (map, key) pair for the nth entry.
+ '''
+ map = m = {}
+ mstack = []
+ pop = 0
+ nth_pair = None
+ if nth is not None:
+ nth_pair = (None, None)
+ for step in split(s, ':'):
+ if step[:1] == '.':
+ pop = len(step) - 1
+ continue
+ if pop < 0:
+ mstack.append(m)
+ m[obid] = {}
+ m = m[obid]
+ elif map:
+ m[obid] = None
+ if len(step) == 0:
+ return map
+ obid = step
+ if pop > 0:
+ m = mstack[-pop]
+ del mstack[-pop:]
+ pop = -1
+ if nth == 0:
+ nth_pair = (m, obid)
+ nth = None
+ elif nth is not None:
+ nth = nth - 1
+ m[obid] = None
+ if nth == 0:
+ return map, (m, obid)
+ if nth_pair is not None:
+ return map, nth_pair
+ return map
+
diff --git a/ZTUtils/Zope.py b/ZTUtils/Zope.py
--- /dev/null
+++ b/ZTUtils/Zope.py
@@ -0,0 +1,302 @@
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (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
+#
+##############################################################################
+__doc__='''Zope-specific versions of ZTUTils classes
+
+$Id: Zope.py,v 1.1 2002-08-30 08:25:34 richard Exp $'''
+__version__='$Revision: 1.1 $'[11:-2]
+
+import sys, cgi, urllib, cgi
+from Tree import encodeExpansion, decodeExpansion, TreeMaker
+from SimpleTree import SimpleTreeMaker
+from Batch import Batch
+from Products.ZCatalog.Lazy import Lazy
+from AccessControl import getSecurityManager
+from string import split, join
+from types import StringType, ListType, IntType, FloatType
+from DateTime import DateTime
+
+try:
+ from AccessControl.ZopeGuards import guarded_getitem
+except ImportError:
+ Unauthorized = 'Unauthorized'
+ def guarded_getitem(object, index):
+ v = object[index]
+ if getSecurityManager().validate(object, object, index, v):
+ return v
+ raise Unauthorized, 'unauthorized access to element %s' % `i`
+else:
+ from AccessControl import Unauthorized
+
+class LazyFilter(Lazy):
+ # A LazyFilter that checks with the security policy
+
+ def __init__(self, seq, test=None, skip=None):
+ self._seq=seq
+ self._data=[]
+ self._eindex=-1
+ self._test=test
+ if not (skip is None or str(skip) == skip):
+ raise TypeError, 'Skip must be None or a string'
+ self._skip = skip
+
+ def __getitem__(self,index):
+ data=self._data
+ try: s=self._seq
+ except AttributeError: return data[index]
+
+ i=index
+ if i < 0: i=len(self)+i
+ if i < 0: raise IndexError, index
+
+ ind=len(data)
+ if i < ind: return data[i]
+ ind=ind-1
+
+ test=self._test
+ e=self._eindex
+ skip = self._skip
+ while i > ind:
+ e = e + 1
+ try:
+ try: v = guarded_getitem(s, e)
+ except Unauthorized, vv:
+ if skip is None:
+ self._eindex = e
+ msg = '(item %s): %s' % (index, vv)
+ raise Unauthorized, msg, sys.exc_info()[2]
+ skip_this = 1
+ else:
+ skip_this = 0
+ except IndexError:
+ del self._test
+ del self._seq
+ del self._eindex
+ raise IndexError, index
+ if skip_this: continue
+ if skip and not getSecurityManager().checkPermission(skip, v):
+ continue
+ if test is None or test(v):
+ data.append(v)
+ ind=ind+1
+ self._eindex=e
+ return data[i]
+
+class TreeSkipMixin:
+ '''Mixin class to make trees test security, and allow
+ skipping of unauthorized objects. '''
+ skip = None
+ def setSkip(self, skip):
+ self.skip = skip
+ return self
+ def getChildren(self, object):
+ return LazyFilter(self._getChildren(object), skip=self.skip)
+
+class TreeMaker(TreeSkipMixin, TreeMaker):
+ _getChildren = TreeMaker.getChildren
+
+class SimpleTreeMaker(TreeSkipMixin, SimpleTreeMaker):
+ _getChildren = SimpleTreeMaker.getChildren
+ def cookieTree(self, root_object, default_state=None):
+ '''Make a tree with state stored in a cookie.'''
+ tree_pre = self.tree_pre
+ state_name = '%s-state' % tree_pre
+ set_name = '%s-setstate' % tree_pre
+
+ req = root_object.REQUEST
+ state = req.get(state_name)
+ if state:
+ setst = req.form.get(set_name)
+ if setst:
+ st, pn, expid = split(setst, ',')
+ state, (m, obid) = decodeExpansion(state, int(pn))
+ if m is None:
+ pass
+ elif st == 'e':
+ if m[obid] is None:
+ m[obid] = {expid: None}
+ else:
+ m[obid][expid] = None
+ elif st == 'c' and m is not state and obid==expid:
+ del m[obid]
+ else:
+ state = decodeExpansion(state)
+ else:
+ state = default_state
+ tree = self.tree(root_object, state)
+ rows = tree.flat()
+ req.RESPONSE.setCookie(state_name, encodeExpansion(rows))
+ return tree, rows
+
+# Make the Batch class test security, and let it skip unauthorized.
+_Batch = Batch
+class Batch(Batch):
+ def __init__(self, sequence, size, start=0, end=0,
+ orphan=0, overlap=0, skip_unauthorized=None):
+ sequence = LazyFilter(sequence, skip=skip_unauthorized)
+ _Batch.__init__(self, sequence, size, start, end,
+ orphan, overlap)
+
+# These functions are meant to be used together in templates that use
+# trees or batches. For example, given a batch with a 'bstart' query
+# argument, you would use "url_query(request, omit='bstart')" to get
+# the base for the batching links, then append
+# "make_query(bstart=batch.previous.first)" to one and
+# "make_query(bstart=batch.end)" to the other.
+
+def make_query(*args, **kwargs):
+ '''Construct a URL query string, with marshalling markup.
+
+ If there are positional arguments, they must be dictionaries.
+ They are combined with the dictionary of keyword arguments to form
+ a dictionary of query names and values.
+
+ Query names (the keys) must be strings. Values may be strings,
+ integers, floats, or DateTimes, and they may also be lists or
+ namespaces containing these types. Names and string values
+ should not be URL-quoted. All arguments are marshalled with
+ complex_marshal().
+ '''
+
+ d = {}
+ for arg in args:
+ d.update(arg)
+ d.update(kwargs)
+
+ uq = urllib.quote
+ qlist = complex_marshal(d.items())
+ for i in range(len(qlist)):
+ k, m, v = qlist[i]
+ qlist[i] = '%s%s=%s' % (uq(k), m, uq(str(v)))
+
+ return join(qlist, '&')
+
+def make_hidden_input(*args, **kwargs):
+ '''Construct a set of hidden input elements, with marshalling markup.
+
+ If there are positional arguments, they must be dictionaries.
+ They are combined with the dictionary of keyword arguments to form
+ a dictionary of query names and values.
+
+ Query names (the keys) must be strings. Values may be strings,
+ integers, floats, or DateTimes, and they may also be lists or
+ namespaces containing these types. All arguments are marshalled with
+ complex_marshal().
+ '''
+
+ d = {}
+ for arg in args:
+ d.update(arg)
+ d.update(kwargs)
+
+ hq = cgi.escape
+ qlist = complex_marshal(d.items())
+ for i in range(len(qlist)):
+ k, m, v = qlist[i]
+ qlist[i] = ('<input type="hidden" name="%s%s" value="%s">'
+ % (hq(k), m, hq(str(v))))
+
+ return join(qlist, '\n')
+
+def complex_marshal(pairs):
+ '''Add request marshalling information to a list of name-value pairs.
+
+ Names must be strings. Values may be strings,
+ integers, floats, or DateTimes, and they may also be lists or
+ namespaces containing these types.
+
+ The list is edited in place so that each (name, value) pair
+ becomes a (name, marshal, value) triple. The middle value is the
+ request marshalling string. Integer, float, and DateTime values
+ will have ":int", ":float", or ":date" as their marshal string.
+ Lists will be flattened, and the elements given ":list" in
+ addition to their simple marshal string. Dictionaries will be
+ flattened and marshalled using ":record".
+ '''
+ i = len(pairs)
+ while i > 0:
+ i = i - 1
+ k, v = pairs[i]
+ m = ''
+ sublist = None
+ if isinstance(v, StringType):
+ pass
+ elif hasattr(v, 'items'):
+ sublist = []
+ for sk, sv in v.items():
+ sm = simple_marshal(sv)
+ sublist.append(('%s.%s' % (k, sk), '%s:record' % sm, sv))
+ elif isinstance(v, ListType):
+ sublist = []
+ for sv in v:
+ sm = simple_marshal(sv)
+ sublist.append((k, '%s:list' % sm, sv))
+ else:
+ m = simple_marshal(v)
+ if sublist is None:
+ pairs[i] = (k, m, v)
+ else:
+ pairs[i:i + 1] = sublist
+
+ return pairs
+
+def simple_marshal(v):
+ if isinstance(v, StringType):
+ return ''
+ if isinstance(v, IntType):
+ return ':int'
+ if isinstance(v, FloatType):
+ return ':float'
+ if isinstance(v, DateTime):
+ return ':date'
+ return ''
+
+def url_query(request, req_name="URL", omit=None):
+ '''Construct a URL with a query string, using the current request.
+
+ request: the request object
+ req_name: the name, such as "URL1" or "BASEPATH1", to get from request
+ omit: sequence of name of query arguments to omit. If a name
+ contains a colon, it is treated literally. Otherwise, it will
+ match each argument name that starts with the name and a period or colon.
+ '''
+
+ base = request[req_name]
+ qs = request.get('QUERY_STRING', '')
+
+ if qs and omit:
+ qsparts = split(qs, '&')
+
+ if isinstance(omit, StringType):
+ omits = {omit: None}
+ else:
+ omits = {}
+ for name in omit:
+ omits[name] = None
+ omitted = omits.has_key
+
+ unq = urllib.unquote
+ for i in range(len(qsparts)):
+ name = unq(split(qsparts[i], '=', 1)[0])
+ if omitted(name):
+ qsparts[i] = ''
+ name = split(name, ':', 1)[0]
+ if omitted(name):
+ qsparts[i] = ''
+ name = split(name, '.', 1)[0]
+ if omitted(name):
+ qsparts[i] = ''
+
+ qs = join(filter(None, qsparts), '&')
+
+ # We alway append '?' since arguments will be appended to the URL
+ return '%s?%s' % (base, qs)
diff --git a/ZTUtils/__init__.py b/ZTUtils/__init__.py
--- /dev/null
+++ b/ZTUtils/__init__.py
@@ -0,0 +1,31 @@
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (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
+#
+##############################################################################
+__doc__='''Package of template utility classes and functions.
+
+$Id: __init__.py,v 1.1 2002-08-30 08:25:34 richard Exp $'''
+__version__='$Revision: 1.1 $'[11:-2]
+
+from Batch import Batch
+from Iterator import Iterator
+from Tree import TreeMaker, encodeExpansion, decodeExpansion, a2b, b2a
+from SimpleTree import SimpleTreeMaker
+
+import sys
+if sys.modules.has_key('Zope'):
+ del sys
+ __allow_access_to_unprotected_subobjects__ = 1
+ __roles__ = None
+
+ from Zope import Batch, TreeMaker, SimpleTreeMaker, LazyFilter
+ from Zope import url_query, make_query, make_hidden_input
+