ImprovedTableParser

Changeset

6:a5aa61c0be15
2012-02-19 Paul Boddie raw files shortlog changelog graph Added sort column controls in table headers. Separated the sort criteria acquired from the request and from the region so that the column types can be fully specified in the region and retrieved regardless of the request's criteria.
ImprovedTableParser.py (file)
     1.1 --- a/ImprovedTableParser.py	Sun Feb 19 00:54:13 2012 +0100
     1.2 +++ b/ImprovedTableParser.py	Sun Feb 19 13:38:53 2012 +0100
     1.3 @@ -280,32 +280,44 @@
     1.4              ascending = False
     1.5  
     1.6          # Extract the conversion indicator and column index.
     1.7 -
     1.8 -        if column_spec.endswith("n"):
     1.9 -            column = column_spec[:-1]
    1.10 -            fn = to_number
    1.11 -        else:
    1.12 -            column = column_spec
    1.13 -            fn = str
    1.14 -
    1.15          # Ignore badly-specified columns.
    1.16  
    1.17          try:
    1.18 +            column = get_number(column_spec)
    1.19 +            suffix = column_spec[len(column):]
    1.20 +            fn = converters[suffix]
    1.21              sort_columns.append((max(0, int(column) - start), fn, ascending))
    1.22          except ValueError:
    1.23              pass
    1.24  
    1.25      return sort_columns
    1.26  
    1.27 +def get_column_types(sort_columns):
    1.28 +
    1.29 +    """
    1.30 +    Return a dictionary mapping column indexes to conversion functions.
    1.31 +    """
    1.32 +
    1.33 +    d = {}
    1.34 +    for column, fn, ascending in sort_columns:
    1.35 +        d[column] = fn, ascending
    1.36 +    return d
    1.37 +
    1.38 +def get_number(s):
    1.39 +
    1.40 +    "From 's', get any leading number."
    1.41 +
    1.42 +    match = leading_number_regexp.match(s)
    1.43 +    if match:
    1.44 +        return match.group()
    1.45 +    else:
    1.46 +        return ""
    1.47 +
    1.48  def to_number(s):
    1.49  
    1.50      "Convert 's' to a number, discarding any non-numeric trailing data."
    1.51  
    1.52 -    match = leading_number_regexp.match(s)
    1.53 -    if match:
    1.54 -        return int(match.group())
    1.55 -    else:
    1.56 -        raise ValueError, s
    1.57 +    return int(get_number(s))
    1.58  
    1.59  class Sorter:
    1.60  
    1.61 @@ -342,6 +354,80 @@
    1.62  
    1.63          return 0
    1.64  
    1.65 +def write_sort_control(columnnumber, write, sort_columns, column_types, columns, table_name, data_start, start=0):
    1.66 +
    1.67 +    """
    1.68 +    Write a sort control in its own form which provides a list of sort
    1.69 +    descriptions, modifying the 'sort_columns' provided by introducing the given
    1.70 +    column in different positions.
    1.71 +    """
    1.72 +
    1.73 +    option_html = """\
    1.74 +        <option value="%(value)s" %(selected)s>%(label)s</option>
    1.75 +"""
    1.76 +
    1.77 +    # Start with the existing criteria without this column being involved.
    1.78 +
    1.79 +    current_sort_columns = [(column + start, suffixes[fn], not ascending and "d" or "")
    1.80 +        for (column, fn, ascending) in sort_columns]
    1.81 +    revised_sort_columns = [(column + start, suffixes[fn], not ascending and "d" or "")
    1.82 +        for (column, fn, ascending) in sort_columns if column != columnnumber]
    1.83 +    values = [revised_sort_columns]
    1.84 +    revised_sort_labels = [columns[column][1].strip() for (column, fn, ascending) in revised_sort_columns]
    1.85 +    labels = [revised_sort_labels]
    1.86 +
    1.87 +    # Add this column in all possible places in the sorting criteria.
    1.88 +
    1.89 +    i = 0
    1.90 +    while i <= len(revised_sort_columns):
    1.91 +        value = revised_sort_columns[:]
    1.92 +        label = revised_sort_labels[:]
    1.93 +        fn, ascending = column_types.get(columnnumber, (str, True))
    1.94 +        value.insert(i, (columnnumber + start, suffixes[fn], not ascending and "d" or ""))
    1.95 +        label.insert(i, columns[columnnumber][1].strip())
    1.96 +        values.append(value)
    1.97 +        labels.append(label)
    1.98 +        i += 1
    1.99 +
   1.100 +    # Make the list of options.
   1.101 +
   1.102 +    options_html = []
   1.103 +    for value, label in zip(values, labels):
   1.104 +        options_html.append(option_html % {
   1.105 +            "value"     : ",".join([("%d%s%s" % spec) for spec in value]),
   1.106 +            "label"     : ", ".join(label),
   1.107 +            "selected"  : value == current_sort_columns and 'selected="selected"' or "",
   1.108 +            })
   1.109 +
   1.110 +    # Write the form.
   1.111 +
   1.112 +    d = {
   1.113 +        "table_name"    : table_name,
   1.114 +        "options"       : "".join(options_html),
   1.115 +        "data_start"    : data_start,
   1.116 +        }
   1.117 +
   1.118 +    write("""\
   1.119 +<form method="post">
   1.120 +    <input name="tablename" value="%(table_name)s" type="hidden" />
   1.121 +    <input name="%(table_name)s-headers" value="%(data_start)s" type="hidden" />
   1.122 +    <select name="%(table_name)s-sortcolumns" onchange="this.form.submit()">
   1.123 +%(options)s
   1.124 +    </select>
   1.125 +</form>
   1.126 +""" % d)
   1.127 +
   1.128 +# Sorting-related tables.
   1.129 +
   1.130 +converters = {
   1.131 +    "n" : to_number,
   1.132 +    "" : str,
   1.133 +    }
   1.134 +
   1.135 +suffixes = {}
   1.136 +for key, value in converters.items():
   1.137 +    suffixes[value] = key
   1.138 +
   1.139  # Common formatting functions.
   1.140  
   1.141  def formatTable(text, request, fmt, attrs=None):
   1.142 @@ -358,36 +444,53 @@
   1.143      # Override any region arguments with request parameters.
   1.144  
   1.145      table_name = attrs.get("name")
   1.146 -    sortcolumns = table_name and getQualifiedParameter(request, table_name, "sortcolumns") or attrs.get("sortcolumns")
   1.147 +
   1.148 +    # Get sorting criteria from the region and the request.
   1.149 +
   1.150 +    region_sortcolumns = attrs.get("sortcolumns")
   1.151 +    sortcolumns = table_name and getQualifiedParameter(request, table_name, "sortcolumns") or region_sortcolumns
   1.152  
   1.153      # Sort the rows according to the values in each of the specified columns.
   1.154  
   1.155 +    data_start = int(table_name and getQualifiedParameter(request, table_name, "headers") or attrs.get("headers", "1"))
   1.156 +
   1.157      if sortcolumns:
   1.158 -        data_start = int(attrs.get("headers", "1"))
   1.159          headers = table[:data_start]
   1.160          data = table[data_start:]
   1.161  
   1.162          # Get the sort columns using Unix sort-like notation.
   1.163  
   1.164 -        sorter = Sorter(get_sort_columns(sortcolumns))
   1.165 +        sort_columns = get_sort_columns(sortcolumns)
   1.166 +        region_sort_columns = get_sort_columns(region_sortcolumns)
   1.167 +
   1.168 +        sorter = Sorter(sort_columns)
   1.169          data.sort(cmp=sorter)
   1.170  
   1.171          table = headers + data
   1.172 +        column_types = get_column_types(region_sort_columns)
   1.173  
   1.174      # Write the table.
   1.175  
   1.176 -    request.write(fmt.table(1, table_attrs))
   1.177 +    writing_html = request.page.output_mimetype == "text/html"
   1.178 +    write = request.write
   1.179 +    write(fmt.table(1, table_attrs))
   1.180  
   1.181 -    for row_attrs, columns in table:
   1.182 -        request.write(fmt.table_row(1, row_attrs))
   1.183 +    for rownumber, (row_attrs, columns) in enumerate(table):
   1.184 +        write(fmt.table_row(1, row_attrs))
   1.185 +
   1.186 +        for columnnumber, (column_attrs, column_text) in enumerate(columns):
   1.187 +            write(fmt.table_cell(1, column_attrs))
   1.188 +            write(formatText(column_text, request, fmt))
   1.189  
   1.190 -        for column_attrs, column_text in columns:
   1.191 -            request.write(fmt.table_cell(1, column_attrs))
   1.192 -            request.write(formatText(column_text, request, fmt))
   1.193 -            request.write(fmt.table_cell(0))
   1.194 +            # Add sorting controls, if appropriate.
   1.195 +
   1.196 +            if writing_html and sortcolumns and rownumber == data_start - 1:
   1.197 +                write_sort_control(columnnumber, write, sort_columns, column_types, columns, table_name, data_start)
   1.198  
   1.199 -        request.write(fmt.table_row(0))
   1.200 +            write(fmt.table_cell(0))
   1.201  
   1.202 -    request.write(fmt.table(0))
   1.203 +        write(fmt.table_row(0))
   1.204 +
   1.205 +    write(fmt.table(0))
   1.206  
   1.207  # vim: tabstop=4 expandtab shiftwidth=4