Mercurial > p > roundup > code
diff ZTUtils/Tree.py @ 983:7fe79c67aaa9
Adding ZTUtils to the dist
| author | Richard Jones <richard@users.sourceforge.net> |
|---|---|
| date | Fri, 30 Aug 2002 08:25:34 +0000 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ZTUtils/Tree.py Fri Aug 30 08:25:34 2002 +0000 @@ -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 +
