1.1 --- a/ImprovedTableParser.py Tue Feb 21 00:05:55 2012 +0100
1.2 +++ b/ImprovedTableParser.py Thu Feb 23 23:47:36 2012 +0100
1.3 @@ -59,6 +59,11 @@
1.4
1.5 row_attrs = {}
1.6 columns = []
1.7 + columnnumber = 0
1.8 +
1.9 + # The following will be redefined upon the construction of the first column.
1.10 +
1.11 + column_attrs = {}
1.12
1.13 # Process exposed text and sections.
1.14
1.15 @@ -81,13 +86,41 @@
1.16 # Only create a new row when a boundary has been found.
1.17
1.18 if not row_continued:
1.19 +
1.20 + # Complete any existing row.
1.21 +
1.22 if columns:
1.23 extractAttributes(columns[0][0], row_attrs, table_attrs)
1.24 + span_columns(columns, columnnumber)
1.25 +
1.26 + # Replicate the last row to determine column usage.
1.27 +
1.28 + column_usage = []
1.29 +
1.30 + for column_attrs, text in columns:
1.31 + rowspan = int(strip_token(column_attrs.get("rowspan", "1")))
1.32 + if rowspan > 1:
1.33 + attrs = {}
1.34 + attrs.update(column_attrs)
1.35 + attrs["rowspan"] = str(rowspan - 1)
1.36 + attrs["rowcontinuation"] = True
1.37 + column_usage.append((attrs, text))
1.38 + else:
1.39 + column_usage.append(({}, None))
1.40 +
1.41 + columns = column_usage
1.42 +
1.43 + # Define a new collection of row attributes.
1.44
1.45 row_attrs = {}
1.46 - columns = []
1.47 +
1.48 + # Reset the columns and make the list available for the
1.49 + # addition of new columns, starting a new column
1.50 + # immediately.
1.51 +
1.52 rows.append((row_attrs, columns))
1.53 column_continued = False
1.54 + columnnumber = 0
1.55
1.56 # Extract each column from the row.
1.57
1.58 @@ -97,17 +130,42 @@
1.59
1.60 if not column_continued:
1.61
1.62 + # Complete any existing column.
1.63 +
1.64 + if columns:
1.65 + columnnumber = span_columns(columns, columnnumber)
1.66 +
1.67 # Extract the attribute and text sections.
1.68
1.69 match = patterns["column"].search(text)
1.70 if match:
1.71 attribute_text, text = match.groups()
1.72 - columns.append([parseAttributes(attribute_text, True), text])
1.73 + column_attrs = parseAttributes(attribute_text, True)
1.74 else:
1.75 - columns.append([{}, text])
1.76 + column_attrs = {}
1.77 +
1.78 + # Define the new column with a mutable container
1.79 + # permitting the extension of the text.
1.80 +
1.81 + details = [column_attrs, text]
1.82 +
1.83 + # Find the next gap in the columns.
1.84 +
1.85 + while columnnumber != -1 and columnnumber < len(columns):
1.86 + attrs, text = columns[columnnumber]
1.87 + if text is None:
1.88 + columns[columnnumber] = details
1.89 + break
1.90 + columnnumber += 1
1.91 +
1.92 + # Or start adding at the end of the row.
1.93 +
1.94 + else:
1.95 + columnnumber = -1
1.96 + columns.append(details)
1.97
1.98 else:
1.99 - columns[-1][1] += text
1.100 + columns[columnnumber][1] += text
1.101
1.102 # Permit columns immediately following this one.
1.103
1.104 @@ -128,15 +186,55 @@
1.105 # Write any section into the current column.
1.106
1.107 else:
1.108 - columns[-1][1] += region
1.109 + columns[columnnumber][1] += region
1.110
1.111 exposed = not exposed
1.112
1.113 + # Complete any final row.
1.114 +
1.115 if columns:
1.116 extractAttributes(columns[0][0], row_attrs, table_attrs)
1.117
1.118 return table_attrs, rows
1.119
1.120 +def span_columns(columns, columnnumber):
1.121 +
1.122 + """
1.123 + In the 'columns', make the column with the 'columnnumber' span the specified
1.124 + number of columns, returning the next appropriate column number.
1.125 + """
1.126 +
1.127 + column_attrs, text = columns[columnnumber]
1.128 +
1.129 + # Handle any previous column spanning other columns.
1.130 +
1.131 + if column_attrs.has_key("colspan"):
1.132 + colspan = int(strip_token(column_attrs["colspan"]))
1.133 +
1.134 + # Duplicate the current column as continuation
1.135 + # columns for as long as the colspan is defined.
1.136 +
1.137 + colspan -= 1
1.138 + while colspan > 0:
1.139 + attrs = {}
1.140 + attrs.update(column_attrs)
1.141 + attrs["colspan"] = str(colspan)
1.142 + attrs["colcontinuation"] = True
1.143 +
1.144 + if columnnumber != -1:
1.145 + columnnumber += 1
1.146 + if columnnumber < len(columns):
1.147 + columns[columnnumber] = attrs, text
1.148 + else:
1.149 + columnnumber = -1
1.150 +
1.151 + if columnnumber == -1:
1.152 + columns.append((attrs, text))
1.153 +
1.154 + colspan -= 1
1.155 +
1.156 + return columnnumber
1.157 +
1.158 def extractAttributes(attrs, row_attrs, table_attrs):
1.159
1.160 """
1.161 @@ -145,7 +243,7 @@
1.162 """
1.163
1.164 for name, value in attrs.items():
1.165 - if name.startswith("row") and name != "rowspan":
1.166 + if name.startswith("row") and name not in ("rowspan", "rowcontinuation"):
1.167 row_attrs[name] = value
1.168 del attrs[name]
1.169 elif name.startswith("table"):
1.170 @@ -492,8 +590,11 @@
1.171 write(fmt.span(0))
1.172 write(fmt.span(0))
1.173
1.174 - # Link for selection of the modified sort criteria.
1.175 + # Link for selection of the modified sort criteria using the current
1.176 + # column and showing its particular direction.
1.177
1.178 + arrow = ascending and down_arrow or up_arrow
1.179 + arrow_reverse = not ascending and down_arrow or up_arrow
1.180 write_sort_link(write, request, fmt, table_name, sortcolumns, u"%s %s" % (label, arrow), "")
1.181
1.182 # Columns permitting removal or modification.
1.183 @@ -506,8 +607,13 @@
1.184
1.185 if just_had_this_column:
1.186 just_had_this_column = False
1.187 + arrow = ascending and down_arrow or up_arrow
1.188 + arrow_reverse = not ascending and down_arrow or up_arrow
1.189 +
1.190 + # Write the current column with its particular direction.
1.191 +
1.192 write(fmt.span(1, css_class="unlinkedcolumn"))
1.193 - write(formatText(label, request, fmt))
1.194 + write(formatText(u"%s %s" % (label, arrow), request, fmt))
1.195 write(fmt.span(0))
1.196
1.197 # Or show the column with a link for its removal.
1.198 @@ -592,54 +698,53 @@
1.199 else:
1.200 table_name = table_attrs.get("tableid")
1.201
1.202 - # Get the underlying column types.
1.203 -
1.204 - column_types = get_column_types(get_sort_columns(attrs.get("columntypes", "")))
1.205 -
1.206 - # Get sorting criteria from the region.
1.207 -
1.208 - region_sortcolumns = attrs.get("sortcolumns", "")
1.209 -
1.210 - # Update the column types from the sort criteria.
1.211 -
1.212 - column_types.update(get_column_types(get_sort_columns(region_sortcolumns)))
1.213 -
1.214 - # Determine the applicable sort criteria using the request.
1.215 + # Only attempt to offer sorting capabilities if a table name is specified.
1.216
1.217 if table_name:
1.218 - sortcolumns = getQualifiedParameter(request, table_name, "sortcolumns")
1.219 - else:
1.220 - sortcolumns = None
1.221 +
1.222 + # Get the underlying column types.
1.223
1.224 - if sortcolumns is None:
1.225 - sortcolumns = region_sortcolumns
1.226 + column_types = get_column_types(get_sort_columns(attrs.get("columntypes", "")))
1.227 +
1.228 + # Get sorting criteria from the region.
1.229 +
1.230 + region_sortcolumns = attrs.get("sortcolumns", "")
1.231
1.232 - # Define the final sort criteria.
1.233 + # Update the column types from the sort criteria.
1.234
1.235 - sort_columns = get_sort_columns(sortcolumns)
1.236 + column_types.update(get_column_types(get_sort_columns(region_sortcolumns)))
1.237 +
1.238 + # Determine the applicable sort criteria using the request.
1.239
1.240 - # Update the column types from the final sort criteria.
1.241 + sortcolumns = getQualifiedParameter(request, table_name, "sortcolumns")
1.242 + if sortcolumns is None:
1.243 + sortcolumns = region_sortcolumns
1.244
1.245 - column_types.update(get_column_types(sort_columns))
1.246 + # Define the final sort criteria.
1.247
1.248 - # Sort the rows according to the values in each of the specified columns.
1.249 -
1.250 - data_start = int(table_name and getQualifiedParameter(request, table_name, "headers") or attrs.get("headers", "1"))
1.251 + sort_columns = get_sort_columns(sortcolumns)
1.252 + data_start = int(getQualifiedParameter(request, table_name, "headers") or attrs.get("headers", "1"))
1.253
1.254 - if sort_columns:
1.255 - headers = table[:data_start]
1.256 - data = table[data_start:]
1.257 + # Update the column types from the final sort criteria.
1.258 +
1.259 + column_types.update(get_column_types(sort_columns))
1.260 +
1.261 + # Sort the rows according to the values in each of the specified columns.
1.262
1.263 - # Perform the sort and reconstruct the table.
1.264 + if sort_columns:
1.265 + headers = table[:data_start]
1.266 + data = table[data_start:]
1.267 +
1.268 + # Perform the sort and reconstruct the table.
1.269
1.270 - sorter = Sorter(sort_columns, request)
1.271 - data.sort(cmp=sorter)
1.272 - table = headers + data
1.273 + sorter = Sorter(sort_columns, request)
1.274 + data.sort(cmp=sorter)
1.275 + table = headers + data
1.276
1.277 - # Permit sorting because column types for sorting may have been defined.
1.278 + # Otherwise, indicate that no sorting is being performed.
1.279
1.280 else:
1.281 - sort_columns = []
1.282 + sort_columns = None
1.283
1.284 # Write the table.
1.285
1.286 @@ -648,19 +753,44 @@
1.287
1.288 for rownumber, (row_attrs, columns) in enumerate(table):
1.289 write(fmt.table_row(1, row_attrs))
1.290 - sortable = column_types and rownumber == data_start - 1
1.291 + sortable_heading = sort_columns is not None and rownumber == data_start - 1
1.292
1.293 for columnnumber, (column_attrs, column_text) in enumerate(columns):
1.294 +
1.295 + # Always skip column continuation cells.
1.296 +
1.297 + if column_attrs.get("colcontinuation"):
1.298 + continue
1.299 +
1.300 + # Where sorting has not occurred, preserve rowspans and do not write
1.301 + # cells that continue a rowspan.
1.302 +
1.303 + if not sort_columns:
1.304 + if column_attrs.get("rowcontinuation"):
1.305 + continue
1.306 +
1.307 + # Where sorting has occurred, replicate cell contents and remove any
1.308 + # rowspans.
1.309 +
1.310 + else:
1.311 + if column_attrs.has_key("rowspan"):
1.312 + del column_attrs["rowspan"]
1.313 +
1.314 + # Remove any continuation attributes that still apply.
1.315 +
1.316 + if column_attrs.has_key("rowcontinuation"):
1.317 + del column_attrs["rowcontinuation"]
1.318 +
1.319 write(fmt.table_cell(1, column_attrs))
1.320
1.321 - if sortable:
1.322 + if sortable_heading:
1.323 write(fmt.div(1, css_class="sortablecolumn"))
1.324
1.325 - write(formatText(column_text, request, fmt))
1.326 + write(formatText(column_text or "", request, fmt))
1.327
1.328 # Add sorting controls, if appropriate.
1.329
1.330 - if sortable:
1.331 + if sortable_heading:
1.332 write_sort_control(request, columnnumber, columns, sort_columns, column_types, table_name)
1.333 write(fmt.div(0))
1.334