# HG changeset patch # User Paul Boddie # Date 1330037256 -3600 # Node ID 2a15a0299b4e6b69ef5ab91a7439579cfd66cca6 # Parent cdef4cbc9a6137c64babe76d38923fa8bde53f60 Added support for column and row spans so that the contents of a table can still be sorted through the definition of a uniform grid of cells whose contents can then be compared on a row-by-row basis. Fixed sort direction labelling in the sort controls. Tidied up the sorting logic, adding support for sorting where only the headers and name attributes have been defined for a table. diff -r cdef4cbc9a61 -r 2a15a0299b4e ImprovedTableParser.py --- a/ImprovedTableParser.py Tue Feb 21 00:05:55 2012 +0100 +++ b/ImprovedTableParser.py Thu Feb 23 23:47:36 2012 +0100 @@ -59,6 +59,11 @@ row_attrs = {} columns = [] + columnnumber = 0 + + # The following will be redefined upon the construction of the first column. + + column_attrs = {} # Process exposed text and sections. @@ -81,13 +86,41 @@ # Only create a new row when a boundary has been found. if not row_continued: + + # Complete any existing row. + if columns: extractAttributes(columns[0][0], row_attrs, table_attrs) + span_columns(columns, columnnumber) + + # Replicate the last row to determine column usage. + + column_usage = [] + + for column_attrs, text in columns: + rowspan = int(strip_token(column_attrs.get("rowspan", "1"))) + if rowspan > 1: + attrs = {} + attrs.update(column_attrs) + attrs["rowspan"] = str(rowspan - 1) + attrs["rowcontinuation"] = True + column_usage.append((attrs, text)) + else: + column_usage.append(({}, None)) + + columns = column_usage + + # Define a new collection of row attributes. row_attrs = {} - columns = [] + + # Reset the columns and make the list available for the + # addition of new columns, starting a new column + # immediately. + rows.append((row_attrs, columns)) column_continued = False + columnnumber = 0 # Extract each column from the row. @@ -97,17 +130,42 @@ if not column_continued: + # Complete any existing column. + + if columns: + columnnumber = span_columns(columns, columnnumber) + # Extract the attribute and text sections. match = patterns["column"].search(text) if match: attribute_text, text = match.groups() - columns.append([parseAttributes(attribute_text, True), text]) + column_attrs = parseAttributes(attribute_text, True) else: - columns.append([{}, text]) + column_attrs = {} + + # Define the new column with a mutable container + # permitting the extension of the text. + + details = [column_attrs, text] + + # Find the next gap in the columns. + + while columnnumber != -1 and columnnumber < len(columns): + attrs, text = columns[columnnumber] + if text is None: + columns[columnnumber] = details + break + columnnumber += 1 + + # Or start adding at the end of the row. + + else: + columnnumber = -1 + columns.append(details) else: - columns[-1][1] += text + columns[columnnumber][1] += text # Permit columns immediately following this one. @@ -128,15 +186,55 @@ # Write any section into the current column. else: - columns[-1][1] += region + columns[columnnumber][1] += region exposed = not exposed + # Complete any final row. + if columns: extractAttributes(columns[0][0], row_attrs, table_attrs) return table_attrs, rows +def span_columns(columns, columnnumber): + + """ + In the 'columns', make the column with the 'columnnumber' span the specified + number of columns, returning the next appropriate column number. + """ + + column_attrs, text = columns[columnnumber] + + # Handle any previous column spanning other columns. + + if column_attrs.has_key("colspan"): + colspan = int(strip_token(column_attrs["colspan"])) + + # Duplicate the current column as continuation + # columns for as long as the colspan is defined. + + colspan -= 1 + while colspan > 0: + attrs = {} + attrs.update(column_attrs) + attrs["colspan"] = str(colspan) + attrs["colcontinuation"] = True + + if columnnumber != -1: + columnnumber += 1 + if columnnumber < len(columns): + columns[columnnumber] = attrs, text + else: + columnnumber = -1 + + if columnnumber == -1: + columns.append((attrs, text)) + + colspan -= 1 + + return columnnumber + def extractAttributes(attrs, row_attrs, table_attrs): """ @@ -145,7 +243,7 @@ """ for name, value in attrs.items(): - if name.startswith("row") and name != "rowspan": + if name.startswith("row") and name not in ("rowspan", "rowcontinuation"): row_attrs[name] = value del attrs[name] elif name.startswith("table"): @@ -492,8 +590,11 @@ write(fmt.span(0)) write(fmt.span(0)) - # Link for selection of the modified sort criteria. + # Link for selection of the modified sort criteria using the current + # column and showing its particular direction. + arrow = ascending and down_arrow or up_arrow + arrow_reverse = not ascending and down_arrow or up_arrow write_sort_link(write, request, fmt, table_name, sortcolumns, u"%s %s" % (label, arrow), "") # Columns permitting removal or modification. @@ -506,8 +607,13 @@ if just_had_this_column: just_had_this_column = False + arrow = ascending and down_arrow or up_arrow + arrow_reverse = not ascending and down_arrow or up_arrow + + # Write the current column with its particular direction. + write(fmt.span(1, css_class="unlinkedcolumn")) - write(formatText(label, request, fmt)) + write(formatText(u"%s %s" % (label, arrow), request, fmt)) write(fmt.span(0)) # Or show the column with a link for its removal. @@ -592,54 +698,53 @@ else: table_name = table_attrs.get("tableid") - # Get the underlying column types. - - column_types = get_column_types(get_sort_columns(attrs.get("columntypes", ""))) - - # Get sorting criteria from the region. - - region_sortcolumns = attrs.get("sortcolumns", "") - - # Update the column types from the sort criteria. - - column_types.update(get_column_types(get_sort_columns(region_sortcolumns))) - - # Determine the applicable sort criteria using the request. + # Only attempt to offer sorting capabilities if a table name is specified. if table_name: - sortcolumns = getQualifiedParameter(request, table_name, "sortcolumns") - else: - sortcolumns = None + + # Get the underlying column types. - if sortcolumns is None: - sortcolumns = region_sortcolumns + column_types = get_column_types(get_sort_columns(attrs.get("columntypes", ""))) + + # Get sorting criteria from the region. + + region_sortcolumns = attrs.get("sortcolumns", "") - # Define the final sort criteria. + # Update the column types from the sort criteria. - sort_columns = get_sort_columns(sortcolumns) + column_types.update(get_column_types(get_sort_columns(region_sortcolumns))) + + # Determine the applicable sort criteria using the request. - # Update the column types from the final sort criteria. + sortcolumns = getQualifiedParameter(request, table_name, "sortcolumns") + if sortcolumns is None: + sortcolumns = region_sortcolumns - column_types.update(get_column_types(sort_columns)) + # Define the final sort criteria. - # Sort the rows according to the values in each of the specified columns. - - data_start = int(table_name and getQualifiedParameter(request, table_name, "headers") or attrs.get("headers", "1")) + sort_columns = get_sort_columns(sortcolumns) + data_start = int(getQualifiedParameter(request, table_name, "headers") or attrs.get("headers", "1")) - if sort_columns: - headers = table[:data_start] - data = table[data_start:] + # Update the column types from the final sort criteria. + + column_types.update(get_column_types(sort_columns)) + + # Sort the rows according to the values in each of the specified columns. - # Perform the sort and reconstruct the table. + if sort_columns: + headers = table[:data_start] + data = table[data_start:] + + # Perform the sort and reconstruct the table. - sorter = Sorter(sort_columns, request) - data.sort(cmp=sorter) - table = headers + data + sorter = Sorter(sort_columns, request) + data.sort(cmp=sorter) + table = headers + data - # Permit sorting because column types for sorting may have been defined. + # Otherwise, indicate that no sorting is being performed. else: - sort_columns = [] + sort_columns = None # Write the table. @@ -648,19 +753,44 @@ for rownumber, (row_attrs, columns) in enumerate(table): write(fmt.table_row(1, row_attrs)) - sortable = column_types and rownumber == data_start - 1 + sortable_heading = sort_columns is not None and rownumber == data_start - 1 for columnnumber, (column_attrs, column_text) in enumerate(columns): + + # Always skip column continuation cells. + + if column_attrs.get("colcontinuation"): + continue + + # Where sorting has not occurred, preserve rowspans and do not write + # cells that continue a rowspan. + + if not sort_columns: + if column_attrs.get("rowcontinuation"): + continue + + # Where sorting has occurred, replicate cell contents and remove any + # rowspans. + + else: + if column_attrs.has_key("rowspan"): + del column_attrs["rowspan"] + + # Remove any continuation attributes that still apply. + + if column_attrs.has_key("rowcontinuation"): + del column_attrs["rowcontinuation"] + write(fmt.table_cell(1, column_attrs)) - if sortable: + if sortable_heading: write(fmt.div(1, css_class="sortablecolumn")) - write(formatText(column_text, request, fmt)) + write(formatText(column_text or "", request, fmt)) # Add sorting controls, if appropriate. - if sortable: + if sortable_heading: write_sort_control(request, columnnumber, columns, sort_columns, column_types, table_name) write(fmt.div(0))