############################################################################## # # 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 # ############################################################################## """ Advanced sort support by Oleg Broytmann 23 Apr 2001 eg Sort(sequence, (("akey", "nocase"), ("anotherkey", "cmp", "desc"))) $Id: SortEx.py 68493 2006-06-05 09:57:28Z andreasjung $ """ from App.config import getConfiguration def sort(sequence, sort=(), _=None, mapping=0): """ - sequence is a sequence of objects to be sorted - sort is a sequence of tuples (key,func,direction) that define the sort order: - key is the name of an attribute to sort the objects by - func is the name of a comparison function. This parameter is optional allowed values: - "cmp" -- the standard comparison function (default) - "nocase" -- case-insensitive comparison - "strcoll" or "locale" -- locale-aware string comparison - "strcoll_nocase" or "locale_nocase" -- locale-aware case-insensitive string comparison - "xxx" -- a user-defined comparison function - direction -- defines the sort direction for the key (optional). (allowed values: "asc" (default) , "desc") """ need_sortfunc = 0 if sort: for s in sort: if len(s) > 1: # extended sort if there is reference to... # ...comparison function or sort order, even if they are "cmp" and "asc" need_sortfunc = 1 break sortfields = sort # multi sort = key1,key2 multsort = len(sortfields) > 1 # flag: is multiple sort if need_sortfunc: # prepare the list of functions and sort order multipliers sf_list = make_sortfunctions(sortfields, _) # clean the mess a bit if multsort: # More than one sort key. sortfields = map(lambda x: x[0], sf_list) else: sort = sf_list[0][0] elif sort: if multsort: # More than one sort key. sortfields = map(lambda x: x[0], sort) else: sort = sort[0][0] isort=not sort s=[] for client in sequence: k = None if isinstance(client, tuple) and len(client)==2: if isort: k=client[0] v=client[1] else: if isort: k=client v=client if sort: if multsort: # More than one sort key. k = [] for sk in sortfields: try: if mapping: akey = v[sk] else: akey = getattr(v, sk) except (AttributeError, KeyError): akey = None if not basic_type(type(akey)): try: akey = akey() except: pass k.append(akey) else: # One sort key. try: if mapping: k = v[sort] else: k = getattr(v, sort) except (AttributeError, KeyError): k = None if not basic_type(type(k)): try: k = k() except: pass s.append((k,client)) if need_sortfunc: by = SortBy(multsort, sf_list) s.sort(by) else: s.sort() sequence=[] for k, client in s: sequence.append(client) return sequence SortEx = sort basic_type={type(''): 1, type(0): 1, type(0.0): 1, type(()): 1, type([]): 1, type(None) : 1 }.has_key def nocase(str1, str2): return cmp(str1.lower(), str2.lower()) def getStrcoll(): if getConfiguration().locale: from locale import strcoll return strcoll else: raise RuntimeError("strcoll() is only available for a proper 'locale' configuration in zope.conf") def getStrcoll_nocase(): if getConfiguration().locale: from locale import strcoll return strcoll def strcoll_nocase(str1, str2): return strcoll(str1.lower(), str2.lower()) return strcoll_nocase else: raise RuntimeError("strcoll() is only available for a proper 'locale' configuration in zope.conf") def make_sortfunctions(sortfields, _): """Accepts a list of sort fields; splits every field, finds comparison function. Returns a list of 3-tuples (field, cmp_function, asc_multplier)""" sf_list = [] for field in sortfields: f = list(field) l = len(f) if l == 1: f.append("cmp") f.append("asc") elif l == 2: f.append("asc") elif l == 3: pass else: raise SyntaxError, "sort option must contains no more than 2 fields" f_name = f[1] # predefined function? if f_name == "cmp": func = cmp # builtin elif f_name == "nocase": func = nocase elif f_name in ("locale", "strcoll"): func = getStrcoll() elif f_name in ("locale_nocase", "strcoll_nocase"): func = getStrcoll_nocase() else: # no - look it up in the namespace func = _.getitem(f_name, 0) sort_order = f[2].lower() if sort_order == "asc": multiplier = +1 elif sort_order == "desc": multiplier = -1 else: raise SyntaxError, "sort direction must be either ASC or DESC" sf_list.append((f[0], func, multiplier)) return sf_list class SortBy: def __init__(self, multsort, sf_list): self.multsort = multsort self.sf_list = sf_list def __call__(self, o1, o2): multsort = self.multsort if multsort: o1 = o1[0] # if multsort - take the first element (key list) o2 = o2[0] sf_list = self.sf_list l = len(sf_list) # assert that o1 and o2 are tuples of apropriate length assert len(o1) == l + 1 - multsort, "%s, %d" % (o1, l + multsort) assert len(o2) == l + 1 - multsort, "%s, %d" % (o2, l + multsort) # now run through the list of functions in sf_list and # compare every object in o1 and o2 for i in range(l): # if multsort - we already extracted the key list # if not multsort - i is 0, and the 0th element is the key c1, c2 = o1[i], o2[i] func, multiplier = sf_list[i][1:3] n = func(c1, c2) if n: return n*multiplier # all functions returned 0 - identical sequences return 0