[Zope-Checkins] CVS: Zope2 - DT_In.py:1.47

andreas@serenade.digicool.com andreas@serenade.digicool.com
Thu, 12 Apr 2001 10:53:53 -0400


Update of /cvs-repository/Zope2/lib/python/DocumentTemplate
In directory serenade.digicool.com:/tmp/cvs-serv7939

Modified Files:
	DT_In.py 
Log Message:
added patch of Collector #2120



--- Updated File DT_In.py in package Zope2 --
--- DT_In.py	2001/03/08 18:35:39	1.46
+++ DT_In.py	2001/04/12 14:53:53	1.47
@@ -121,12 +121,30 @@
 
     Attributes
 
-      sort -- Define the sort order for sequence items.  If an item in
-      the sequence does not define 
+      sort -- Define the sort order for sequence items.  Parameter to the
+      attribute is either a sort option, or list of sort options separated
+      by comma.  Every sort option consists of variable name, optional
+      comparison function name (default is cmp) and optional sort order
+      (default is asc).
+          Examples: sort="date" or sort="date,time" or
+      sort="title/locale,date/cmp/desc". If you want to specify sort order,
+      you cannot omit the function; use cmp for standard comparison.
+          Few predefined comparison functions available: standard cmp,
+      nocase (ignore string case), strcoll (alias "locale"),
+      strcoll_nocase (alias "locale_nocase"). Locale functions are
+      available only if module locale is already imported (you started Zope
+      with -L locale).
+
+      sort_expr -- The "sort" attribute accepts only static list of
+      sort options. This calculated parameter allows you to calculate the
+      list of sort options on the fly.
 
       reverse -- Reverse the sequence (may be combined with sort).  Note
       that this can cause a huge memory use in lazy activation instances. 
 
+      reverse_expr -- This calculated parameter allows you to calculate the
+      need of reversing on the fly.
+
       Within an 'in' block, variables are substituted from the
       elements of the iteration.  The elements may be either
       instance or mapping objects.  In addition, the variables:
@@ -186,7 +204,7 @@
     Batch sequence insertion
 
       When displaying a large number of objects, it is sometimes
-      desireable to display just a sub-sequence of the data.
+      desirable to display just a sub-sequence of the data.
       An 'in' command may have optional parameters,
       as in::
 
@@ -240,14 +258,14 @@
                  <!--#/in-->
 
              If the original URL is: 'foo/bar?x=1&y=2', then the
-             rendered text (after row data are displated) will be::
+             rendered text (after row data are displayed) will be::
 
                       <a href="foo/bar?x=1&y=2&batch_start=20">
                       (Next 20 results)
                       </a>
 
              If the original URL is: 'foo/bar?batch_start=10&x=1&y=2',
-             then the rendered text (after row data are displated)
+             then the rendered text (after row data are displayed)
              will be::
 
                       <a href="foo/bar?x=1&y=2&batch_start=30">
@@ -353,16 +371,16 @@
         mean -- The mean of numeric values values.
 
         variance -- The variance of numeric values computed with a
-          degrees of freedom qeual to the count - 1.
+          degrees of freedom equal to the count - 1.
 
         variance-n -- The variance of numeric values computed with a
-          degrees of freedom qeual to the count.
+          degrees of freedom equal to the count.
 
         standard-deviation -- The standard deviation of numeric values
-          computed with a degrees of freedom qeual to the count - 1.
+          computed with a degrees of freedom equal to the count - 1.
 
         standard-deviation-n -- The standard deviation of numeric
-          values computed with a degrees of freedom qeual to the count.
+          values computed with a degrees of freedom equal to the count.
 
       Missing values are either 'None' or the attribute 'Value'
       of the module 'Missing', if present.
@@ -387,7 +405,7 @@
 
 from DT_Util import ParseError, parse_params, name_param, str
 from DT_Util import render_blocks, InstanceDict, ValidationError, VSEval, expr_globals
-from string import find, atoi, join, split
+from string import find, atoi, join, split, lower
 import ts_regex
 from DT_InSV import sequence_variables, opt
 TupleType=type(())
@@ -499,9 +517,9 @@
 
         if self.sort_expr is not None:
             self.sort=self.sort_expr.eval(md)
-            sequence=self.sort_sequence(sequence)
+            sequence=self.sort_sequence(sequence, md)
         elif self.sort is not None:
-            sequence=self.sort_sequence(sequence)
+            sequence=self.sort_sequence(sequence, md)
 
         if self.reverse_expr is not None and self.reverse_expr.eval(md):
             sequence=self.reverse_sequence(sequence)
@@ -663,9 +681,9 @@
 
         if self.sort_expr is not None:
             self.sort=self.sort_expr.eval(md)
-            sequence=self.sort_sequence(sequence)
+            sequence=self.sort_sequence(sequence, md)
         elif self.sort is not None:
-            sequence=self.sort_sequence(sequence)
+            sequence=self.sort_sequence(sequence, md)
 
         if self.reverse_expr is not None and self.reverse_expr.eval(md):
             sequence=self.reverse_sequence(sequence)
@@ -722,17 +740,35 @@
 
         return result
 
-    def sort_sequence(self, sequence):
+    def sort_sequence(self, sequence, md):
 
         # Modified with multiple sort fields by Ross Lazarus
         # April 7 2000 rossl@med.usyd.edu.au
-        # eg <dtml in "foo" sort=akey,anotherkey>
+        # eg <dtml-in "foo" sort="akey,anotherkey">
         
+        # Modified with advanced sort functions by
+        # Oleg Broytmann <phd@phd.pp.ru> 30 Mar 2001
+        # eg <dtml-in "foo" sort="akey/nocase,anotherkey/cmp/desc">
+
         sort=self.sort
-        sortfields = split(sort,',')   # multi sort = key1,key2 
+        need_sortfunc = find(sort, '/') >= 0
+
+        sortfields = split(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, md)
+
+            # 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]
+
         mapping=self.mapping
         isort=not sort
+
         s=[]
         for client in sequence:
             k = None
@@ -766,7 +802,11 @@
 
             s.append((k,client))
 
-        s.sort()
+        if need_sortfunc:
+            by = SortBy(multsort, sf_list)
+            s.sort(by)
+        else:
+            s.sort()
 
         sequence=[]
         for k, client in s:
@@ -791,3 +831,95 @@
             v=md[v]
             if type(v) is st: v=atoi(v)
     return v
+
+
+# phd: Advanced sort support
+
+def nocase(str1, str2):
+    return cmp(lower(str1), lower(str2))
+
+import sys
+if sys.modules.has_key("locale"): # only if locale is already imported
+    from locale import strcoll
+
+    def strcoll_nocase(str1, str2):
+        return strcoll(lower(str1), lower(str2))
+
+
+def make_sortfunctions(sortfields, md):
+    """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 = split(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 slashes"
+
+        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 = strcoll
+        elif f_name in ("locale_nocase", "strcoll_nocase"):
+            func = strcoll_nocase
+        else: # no - look it up in the namespace
+            func = md.getitem(f_name, 0)
+
+        sort_order = lower(f[2])
+
+        if sort_order == "asc":
+            multiplier = +1
+        elif sort_order == "desc":
+            multiplier = -1
+        else:
+            raise SyntaxError, "sort oder 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