# Plugin for TWiki Enterprise Collaboration Platform, http://TWiki.org/ # # Copyright (C) 2001-2003 John Talintyre, jet@cheerful.com # Copyright (C) 2001-2004 Peter Thoeny, peter@thoeny.org # Copyright (C) 2005-2006 TWiki Contributors # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. For # more details read LICENSE in the root of this distribution. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details, published at # http://www.gnu.org/copyleft/gpl.html # # As per the GPL, removal of this notice is prohibited. use strict; package TWiki::Plugins::TablePlugin::Core; use Time::Local; use vars qw( $translationToken $insideTABLE $tableCount @curTable $sortCol $maxSortCols $requestedTable $up $sortTablesInText $sortAttachments $currTablePre $sortColFromUrl $tableWidth @columnWidths $tableBorder $tableFrame $tableRules $cellPadding $cellSpacing $cellBorder @headerAlign @dataAlign $vAlign $headerVAlign $dataVAlign $headerBg $headerBgSorted $headerColor $sortAllTables $twoCol @dataBg @dataBgSorted @dataColor @isoMonth $headerRows $footerRows $upchar $downchar $diamondchar $url @isoMonth %mon2num $initSort $initDirection $currentSortDirection @rowspan $pluginAttrs $prefsAttrs $tableId $tableSummary $tableCaption $iconUrl $unsortEnabled %sortDirection %columnType ); BEGIN { $translationToken = "\0"; $currTablePre = ''; $upchar = ''; $downchar = ''; $diamondchar = ''; @isoMonth = ( 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ); { my $count = 0; %mon2num = map { $_ => $count++ } @isoMonth; } %sortDirection = ( 'ASCENDING', 0, 'DESCENDING', 1, 'NONE', 2 ); %columnType = ( 'TEXT', 'text', 'DATE', 'date', 'NUMBER', 'number', 'UNDEFINED', 'undefined' ); # the maximum number of columns we will handle $maxSortCols = 10000; $iconUrl = TWiki::Func::getPubUrlPath() . '/' . $TWiki::Plugins::TablePlugin::installWeb . '/TablePlugin/'; $unsortEnabled = 1; # if true, table columns can be unsorted } sub _setDefaults { $sortAllTables = $sortTablesInText; $tableBorder = 1; $tableFrame = ''; $tableRules = ''; $cellSpacing = 0; $cellPadding = 0; $cellBorder = ''; $tableWidth = ''; @columnWidths = (); $headerRows = 1; $footerRows = 0; @headerAlign = (); @dataAlign = (); $vAlign = ''; $headerVAlign = ''; $dataVAlign = ''; $headerBg = '#6b7f93'; $headerBgSorted = ''; $headerColor = '#ffffff'; @dataBg = ( '#ecf2f8', '#ffffff' ); @dataBgSorted = (); @dataColor = (); $tableId = ''; $tableSummary = ''; $tableCaption = ''; undef $initSort; _parseParameters($pluginAttrs); _parseParameters($prefsAttrs); # Preferences setting } # Table attributes defined as a Plugin setting, a preferences setting # e.g. in WebPreferences or as a %TABLE{...}% setting sub _parseParameters { my ($args) = @_; return '' unless ( defined($args) ); return '' unless ( $args =~ /\S/ ); my %params = TWiki::Func::extractParameters($args); # Defines which column to initially sort : ShawnBradford 20020221 my $tmp = $params{initsort}; $initSort = $tmp if ($tmp); # Defines which direction to sort the column set by initsort : # ShawnBradford 20020221 $tmp = $params{initdirection}; $initDirection = $sortDirection{'ASCENDING'} if ( defined $tmp && $tmp =~ /^down$/i ); $initDirection = $sortDirection{'DESCENDING'} if ( defined $tmp && $tmp =~ /^up$/i ); $tmp = $params{sort}; $tmp = '0' if ( defined $tmp && $tmp =~ /^off$/oi ); $sortAllTables = $tmp if ( defined $tmp && $tmp ne '' ); $tmp = $params{tableborder}; $tableBorder = $tmp if ( defined $tmp && $tmp ne '' ); $tmp = $params{tableframe}; $tableFrame = $tmp if ( defined $tmp && $tmp ne '' ); $tmp = $params{tablerules}; $tableRules = $tmp if ( defined $tmp && $tmp ne '' ); $tmp = $params{cellpadding}; $cellPadding = $tmp if ( defined $tmp && $tmp ne '' ); $tmp = $params{cellspacing}; $cellSpacing = $tmp if ( defined $tmp && $tmp ne '' ); $tmp = $params{cellborder}; $cellBorder = $tmp if ( defined $tmp && $tmp ne '' ); $tmp = $params{headeralign}; @headerAlign = split( /,\s*/, $tmp ) if ( defined $tmp ); $tmp = $params{dataalign}; @dataAlign = split( /,\s*/, $tmp ) if ( defined $tmp ); $tmp = $params{tablewidth}; $tableWidth = $tmp if ( defined $tmp ); $tmp = $params{columnwidths}; @columnWidths = split( /, */, $tmp ) if ( defined $tmp ); $tmp = $params{headerrows}; $headerRows = $tmp if ( defined $tmp && $tmp ne '' ); $headerRows = 1 if ( $headerRows < 1 ); $tmp = $params{footerrows}; $footerRows = $tmp if ( defined $tmp && $tmp ne '' ); $tmp = $params{valign}; $vAlign = $tmp if ( defined $tmp ); $tmp = $params{datavalign}; $dataVAlign = $tmp if ( defined $tmp ); $tmp = $params{headervalign}; $headerVAlign = $tmp if ( defined $tmp ); my $tmpheaderbg = $params{headerbg}; $headerBg = $tmpheaderbg if ( defined $tmpheaderbg ); # only set headerbgsorted color if it is defined in %TABLE{}% attributes # otherwise use headerbg $tmp = $params{headerbgsorted}; if ( defined $tmp ) { $headerBgSorted = $tmp; } elsif ( defined $tmpheaderbg ) { $headerBgSorted = $tmpheaderbg; } $tmp = $params{headercolor}; $headerColor = $tmp if ( defined $tmp ); my $tmpdatabg = $params{databg}; @dataBg = split( /,\s*/, $tmpdatabg ) if ( defined $tmpdatabg ); # only set databgsorted color if it is defined in %TABLE{}% attributes # otherwise use databg $tmp = $params{databgsorted}; if ( defined $tmp ) { @dataBgSorted = split( /,\s*/, $tmp ); } elsif ( defined $tmpdatabg ) { @dataBgSorted = split( /,\s*/, $tmpdatabg ); } $tmp = $params{datacolor}; @dataColor = split( /,\s*/, $tmp ) if ( defined $tmp ); $tmp = $params{id}; $tableId = $tmp if ( defined $tmp ); $tmp = $params{summary}; $tableSummary = $tmp if ( defined $tmp ); $tmp = $params{caption}; $tableCaption = $tmp if ( defined $tmp ); return $currTablePre . ''; } # Convert text to number and date if syntactically possible sub _convertToNumberAndDate { my ($text) = @_; $text =~ s/ / /go; $text =~ s/<\/?nobr>/ /go; my $num = undef; my $date = undef; if ( $text =~ /^\s*$/ ) { $num = 0; $date = 0; } if ( $text =~ m|^\s*([0-9]{1,2})[-\s/]*([A-Z][a-z][a-z])[-\s/]*([0-9]{4})\s*-\s*([0-9][0-9]):([0-9][0-9])| ) { # "31 Dec 2003 - 23:59", "31-Dec-2003 - 23:59", # "31 Dec 2003 - 23:59 - any suffix" $date = timegm( 0, $5, $4, $1, $mon2num{$2}, $3 - 1900 ); } elsif ( $text =~ m|^\s*([0-9]{1,2})[-\s/]([A-Z][a-z][a-z])[-\s/]([0-9]{2,4})\s*$| ) { # "31 Dec 2003", "31 Dec 03", "31-Dec-2003", "31/Dec/2003" my $year = $3; $year += 1900 if ( length($year) == 2 && $year > 80 ); $year += 2000 if ( length($year) == 2 ); $date = timegm( 0, 0, 0, $1, $mon2num{$2}, $year - 1900 ); } elsif ( $text =~ /^\s*[0-9]+(\.[0-9]+)?\s*$/ ) { $num = $text; } return ( $num, $date ); } sub _processTableRow { my ( $thePre, $theRow ) = @_; $currTablePre = $thePre || ''; my $span = 0; my $l1 = 0; my $l2 = 0; if ( !$insideTABLE ) { @curTable = (); @rowspan = (); $tableCount++; $currentSortDirection = $sortDirection{'NONE'}; if ( defined $requestedTable && $requestedTable == $tableCount && defined $sortColFromUrl ) { $sortCol = $sortColFromUrl; $sortCol = $maxSortCols if ( $sortCol > $maxSortCols ); $currentSortDirection = _getCurrentSortDirection($up); } elsif ( defined $initSort ) { $sortCol = $initSort - 1; $sortCol = $maxSortCols if ( $sortCol > $maxSortCols ); $currentSortDirection = _getCurrentSortDirection($initDirection); } } $theRow =~ s/\t/ /go; # change tabs to space $theRow =~ s/\s*$//o; # remove trailing spaces $theRow =~ s/(\|\|+)/$translationToken.length($1)."\|"/geo; # calc COLSPAN my $colCount = 0; my @row = (); $span = 0; my $value = ''; foreach ( split( /\|/, $theRow ) ) { my $attr = {}; $attr->{style} = ''; $span = 1; #AS 25-5-01 Fix to avoid matching also single columns if (s/$translationToken([0-9]+)//) { $span = $1; $attr->{colspan} = $span; } s/^\s+$/   /o; ( $l1, $l2 ) = ( 0, 0 ); if (/^(\s*).*?(\s*)$/) { $l1 = length($1); $l2 = length($2); } if ( $l1 >= 2 ) { if ( $l2 <= 1 ) { $attr->{align} = 'right'; } else { $attr->{align} = 'center'; } } if ( defined $columnWidths[$colCount] && $columnWidths[$colCount] && $span <= 2 ) { $attr->{width} = $columnWidths[$colCount]; # CSS style $attr->{style} .= 'width:' . $columnWidths[$colCount] . ';'; } if (/^\s*\^\s*$/) { # row span above $rowspan[$colCount]++; push @row, { text => $value, type => 'Y' }; } else { for ( my $col = $colCount ; $col < ( $colCount + $span ) ; $col++ ) { if ( defined( $rowspan[$col] ) && $rowspan[$col] ) { my $nRows = scalar(@curTable); my $rspan = $rowspan[$col] + 1; if ( $rspan > 1 ) { $curTable[ $nRows - $rspan ][$col]->{attrs}->{rowspan} = $rspan; } undef( $rowspan[$col] ); } } if ( ( (defined $requestedTable && $requestedTable == $tableCount ) || defined $initSort ) && defined $sortCol && $colCount == $sortCol ) { # CSS class name if ( $currentSortDirection == $sortDirection{'ASCENDING'} ) { $attr->{class} = _appendSortedAscendingCssClass( $attr->{class} ); } if ( $currentSortDirection == $sortDirection{'DESCENDING'} ) { $attr->{class} = _appendSortedDescendingCssClass( $attr->{class} ); } } my $type = ''; if (/^\s*\*(.*)\*\s*$/) { $value = $1; if (@headerAlign) { my $align = @headerAlign[ $colCount % ( $#headerAlign + 1 ) ]; $attr->{align} = $align; $attr->{style} .= 'text-align:' . $align . ';'; } if ($headerVAlign) { $attr->{valign} = $headerVAlign if $headerVAlign; $attr->{style} .= 'vertical-align:' . $headerVAlign . ';'; } elsif ($vAlign) { $attr->{valign} = $vAlign; $attr->{style} .= 'vertical-align:' . $vAlign . ';'; } $type = 'th'; } else { if (/^\s*(.*?)\s*$/) { # strip white spaces $_ = $1; } $value = $_; if (@dataAlign) { my $align = @dataAlign[ $colCount % ( $#dataAlign + 1 ) ]; $attr->{align} = $align; $attr->{style} .= 'text-align:' . $align . ';'; } if ($dataVAlign) { $attr->{valign} = $dataVAlign if $dataVAlign; $attr->{style} .= 'vertical-align:' . $dataVAlign . ';'; } elsif ($vAlign) { $attr->{valign} = $vAlign; $attr->{style} .= 'vertical-align:' . $vAlign . ';'; } $type = 'td'; } if ( $cellBorder ne '' ) { my $theCellBorder = $cellBorder; if ( $tableRules eq 'none' ) { # if tablerules are set to none we don't want cell borders anywhere $theCellBorder = 0; } if ( $tableRules eq 'cols' ) { $theCellBorder = 0; } if ( $tableRules eq 'groups' && $type eq 'th' ) { $attr->{style} .= 'border-bottom-style:solid;'; $attr->{style} .= 'border-top-style:solid;'; $attr->{style} .= 'border-left-style:none;'; } if ( $tableRules eq 'groups' && $type eq 'td' ) { $theCellBorder = 0; } $attr->{style} .= 'border-width:' . $theCellBorder . 'px;'; } push @row, { text => $value, attrs => $attr, type => $type }; } while ( $span > 1 ) { push @row, { text => $value, type => 'X' }; $colCount++; $span--; } $colCount++; } push @curTable, \@row; return $currTablePre . ''; # Avoid TWiki converting empty lines to new paras } # Determine whether to generate sorting headers for this table. The header # indicates the context of the table (body or file attachment) sub _shouldISortThisTable { my ($header) = @_; return 0 unless $sortAllTables; # All cells in header are headings? foreach my $cell (@$header) { return 0 if ( $cell->{type} ne 'th' ); } return 1; } # Guess if column is a date, number or plain text sub _guessColumnType { my ($col) = @_; my $isDate = 1; my $isNum = 1; my $num = ''; my $date = ''; my $columnIsValid = 0; foreach my $row (@curTable) { next if ( !$row->[$col]->{text} ); # else $columnIsValid = 1; ( $num, $date ) = _convertToNumberAndDate( $row->[$col]->{text} ); $isDate = 0 if ( !defined($date) ); $isNum = 0 if ( !defined($num) ); last if ( !$isDate && !$isNum ); $row->[$col]->{date} = $date; $row->[$col]->{number} = $num; } return $columnType{'UNDEFINED'} if ( !$columnIsValid ); my $type = $columnType{'TEXT'}; if ($isDate) { $type = $columnType{'DATE'}; } elsif ($isNum) { $type = $columnType{'NUMBER'}; } return $type; } # Remove HTML from text so it can be sorted sub _stripHtml { my ($text) = @_; $text ||= ''; $text =~ s/\ / /go; # convert space $text =~ s/\[\[[^\]]+\]\[([^\]]+)\]\]/$1/go; # extract label from [[...][...]] link $text =~ s/<[^>]+>//go; # strip HTML $text =~ s/^ *//go; # strip leading space space $text = lc($text); # convert to lower case return $text; } # Append space if class names already exist # 'firstClass' becomes 'firstClass ' so 'secondClass' can be appended safely sub _makeCssClassListSafeToAppend { my ($classList) = @_; $classList ||= ''; $classList .= ' ' if length($classList) > 0; return $classList; } # Append CSS class name for "first column" to (possibly) already defined class names sub _appendFirstColumnCssClass { my $classList = _makeCssClassListSafeToAppend(@_); return $classList . 'twikiFirstCol'; } # Append CSS class name for "last row" to (possibly) already defined class names sub _appendLastRowCssClass { my $classList = _makeCssClassListSafeToAppend(@_); return $classList . 'twikiLast'; } # Append CSS class name for "sorted ascending" to (possibly) already defined # class names. sub _appendSortedAscendingCssClass { my $classList = _makeCssClassListSafeToAppend(@_); return $classList . 'twikiSortedAscendingCol'; } # Append CSS class name for "sorted descending" to (possibly) already defined # class names. sub _appendSortedDescendingCssClass { my $classList = _makeCssClassListSafeToAppend(@_); return $classList . 'twikiSortedDescendingCol'; } # The default sort direction. sub _getDefaultSortDirection { return $sortDirection{'ASCENDING'}; } # Gets the current sort direction. sub _getCurrentSortDirection { my ($currentDirection) = @_; $currentDirection ||= _getDefaultSortDirection(); return $currentDirection; } # Gets the new sort direction (needed for sort button) based on the current sort # direction. sub _getNewSortDirection { my ($currentDirection) = @_; if ( !defined $currentDirection ) { return _getDefaultSortDirection(); } my $newDirection; if ( $currentDirection == $sortDirection{'ASCENDING'} ) { $newDirection = $sortDirection{'DESCENDING'}; } if ( $currentDirection == $sortDirection{'DESCENDING'} ) { if ($unsortEnabled) { $newDirection = $sortDirection{'NONE'}; } else { $newDirection = $sortDirection{'ASCENDING'}; } } if ( $currentDirection == $sortDirection{'NONE'} ) { $newDirection = $sortDirection{'ASCENDING'}; } return $newDirection; } sub emitTable { #Validate headerrows/footerrows and modify if out of range if ( $headerRows > @curTable ) { $headerRows = @curTable; # limit header to size of table! } if ( $headerRows + $footerRows > @curTable ) { $footerRows = @curTable - $headerRows; # and footer to whatever is left } my $sortThisTable = _shouldISortThisTable( $curTable[ $headerRows - 1 ] ); my $tattrs = { class => 'twikiTable', border => $tableBorder, cellspacing => $cellSpacing, cellpadding => $cellPadding, style => 'border-width:' . $tableBorder . 'px;' }; $tattrs->{id} = $tableId if ($tableId); $tattrs->{summary} = $tableSummary if ($tableSummary); $tattrs->{frame} = $tableFrame if ($tableFrame); $tattrs->{rules} = $tableRules if ($tableRules); $tattrs->{width} = $tableWidth if ($tableWidth); my $text = $currTablePre . CGI::start_table($tattrs); $text .= $currTablePre . CGI::caption($tableCaption) if ($tableCaption); my $stype = ''; # count the number of cols to prevent looping over non-existing columns my $maxCols = 0; #Flush out any remaining rowspans for ( my $i = 0 ; $i < @rowspan ; $i++ ) { if ( defined( $rowspan[$i] ) && $rowspan[$i] ) { my $nRows = scalar(@curTable); my $rspan = $rowspan[$i] + 1; my $r = $nRows - $rspan; $curTable[$r][$i]->{attrs} ||= {}; if ( $rspan > 1 ) { $curTable[$r][$i]->{attrs}->{rowspan} = $rspan; } } } if ( ( defined $sortCol && defined $requestedTable && $requestedTable == $tableCount ) || defined $initSort ) { # DG 08 Aug 2002: Allow multi-line headers my @header = splice( @curTable, 0, $headerRows ); # DG 08 Aug 2002: Skip sorting any trailers as well my @trailer = (); if ( $footerRows && scalar(@curTable) > $footerRows ) { @trailer = splice( @curTable, -$footerRows ); } # Count the maximum number of columns of this table for my $row ( 0 .. $#curTable ) { my $thisRowMaxColCount = 0; for my $col ( 0 .. $#{ $curTable[$row] } ) { $thisRowMaxColCount++; } $maxCols = $thisRowMaxColCount if ( $thisRowMaxColCount > $maxCols ); } # Handle multi-row labels by killing rowspans in sorted tables for my $row ( 0 .. $#curTable ) { for my $col ( 0 .. $#{ $curTable[$row] } ) { $curTable[$row][$col]->{attrs}->{rowspan} = 1; if ( $curTable[$row][$col]->{type} eq 'Y' ) { $curTable[$row][$col]->{text} = $curTable[ $row - 1 ][$col]->{text}; $curTable[$row][$col]->{type} = 'td'; } } } $stype = $columnType{'UNDEFINED'}; # only get the column type if within bounds if ( $sortCol < $maxCols ) { $stype = _guessColumnType($sortCol); } # invalidate sorting if no valid column if ( $stype eq $columnType{'UNDEFINED'} ) { undef $initSort; undef $sortCol; } elsif ( $stype eq $columnType{'TEXT'} ) { if ( $currentSortDirection == $sortDirection{'DESCENDING'} ) { # efficient way of sorting stripped HTML text # SMELL: efficient? That's not efficient! @curTable = map { $_->[0] } sort { $b->[1] cmp $a->[1] } map { [ $_, _stripHtml( $_->[$sortCol]->{text} ) ] } @curTable; } if ( $currentSortDirection == $sortDirection{'ASCENDING'} ) { @curTable = map { $_->[0] } sort { $a->[1] cmp $b->[1] } map { [ $_, _stripHtml( $_->[$sortCol]->{text} ) ] } @curTable; } } else { if ( $currentSortDirection == $sortDirection{'DESCENDING'} ) { @curTable = sort { $b->[$sortCol]->{$stype} <=> $a->[$sortCol]->{$stype} } @curTable; } if ( $currentSortDirection == $sortDirection{'ASCENDING'} ) { @curTable = sort { $a->[$sortCol]->{$stype} <=> $b->[$sortCol]->{$stype} } @curTable; } } # DG 08 Aug 2002: Cleanup after the header/trailer splicing # this is probably awfully inefficient - but how big is a table? @curTable = ( @header, @curTable, @trailer ); } # if defined $sortCol ... my $rowCount = 0; my $numberOfRows = scalar(@curTable); my $dataColorCount = 0; foreach my $row (@curTable) { my $rowtext = ''; my $colCount = 0; # keep track of header cells: if all cells are header cells, do not # update the data color count my $headerCellCount = 0; my $isHeaderRow = 0; foreach my $fcell (@$row) { # check if cell exists next if ( !$fcell || !$fcell->{type} ); my $tableAnchor = ''; next if ( $fcell->{type} eq 'X' ) ; # data was there so sort could work with col spanning my $type = $fcell->{type}; my $cell = $fcell->{text}; my $attr = $fcell->{attrs} || {}; my $newDirection; my $isSorted = 0; if ( $currentSortDirection != $sortDirection{'NONE'} && defined $sortCol && $colCount == $sortCol # Removing the line below hides the marking of sorted columns # until the user clicks on a header (KJL) # && defined $requestedTable && $requestedTable == $tableCount && $stype ne '' ) { $isSorted = 1; $newDirection = _getNewSortDirection($currentSortDirection); } else { $newDirection = _getDefaultSortDirection(); } if ( $type eq 'th' ) { $headerCellCount++; unless ($upchar) { $upchar = CGI::span( { class => 'tableSortIcon tableSortUp' }, CGI::img( { src => $iconUrl . 'up.gif', alt => 'up' } ) ); $downchar = CGI::span( { class => 'tableSortIcon tableSortDown' }, CGI::img( { src => $iconUrl . 'down.gif', alt => 'down' } ) ); $diamondchar = CGI::span( { class => 'tableSortIcon tableSortUp' }, CGI::img( { src => $iconUrl . 'diamond.gif', border => 0, alt => 'sort' } ) ); } # DG: allow headers without b.g too (consistent and yes, # I use this) $attr->{bgcolor} = $headerBg unless ( $headerBg =~ /none/i ); $attr->{maxCols} = $maxCols; if ( $isSorted ) { if ( $currentSortDirection == $sortDirection{'ASCENDING'} ) { $tableAnchor = CGI::span( { title => 'Sorted ascending' }, $upchar ); } if ( $currentSortDirection == $sortDirection{'DESCENDING'} ) { $tableAnchor = CGI::span( { title => 'Sorted descending' }, $downchar ); } } if ( defined $sortCol && $colCount == $sortCol && defined $requestedTable && $requestedTable == $tableCount ) { $tableAnchor = CGI::a( { name => 'sorted_table' }, '' ) . $tableAnchor; } my $linkColorStyle = ''; if ($headerColor) { #$cell = CGI::span( { style => 'color:'.$headerColor }, $cell ); $linkColorStyle = 'color:' . $headerColor; } if ( $sortThisTable && $rowCount == $headerRows - 1 ) { if ($isSorted) { $attr->{bgcolor} = $headerBgSorted unless ( $headerBgSorted =~ /none/i ); } #my $debugText = CGI::span( { class => 'twikiSmall' }, 'requestedTable='.$requestedTable.'; sortCol='.$sortCol.'; colCount='.$colCount.'; initSort='.$initSort.'; sorted='.$isSorted.'; currDir='.$currentDirection.'; newdir='.$newDirection.' '); my $debugText = ''; my $linkAttributes = { href => $url . 'sortcol=' . $colCount . ';table=' . $tableCount . ';up=' . $newDirection . '#sorted_table', style => $linkColorStyle, rel => 'nofollow', title => 'Sort by this column' }; if ( $cell =~ /\[\[|href/o ) { $cell .= $debugText . ' ' . CGI::a( $linkAttributes, $diamondchar ) . $tableAnchor; } else { $cell = $debugText . CGI::a( $linkAttributes, $cell ) . $tableAnchor; } } else { my $linkAttributes = { style => $linkColorStyle }; $cell = CGI::span( $linkAttributes, ' ' . $cell . ' ' ); } } else { # $type is not 'th' if (@dataBg) { my $bgcolor; if ($isSorted) { $bgcolor = $dataBgSorted[ $dataColorCount % ( $#dataBgSorted + 1 ) ]; } else { $bgcolor = $dataBg[ $dataColorCount % ( $#dataBg + 1 ) ]; } $attr->{bgcolor} = $bgcolor unless ( $bgcolor =~ /none/i ); } if (@dataColor) { my $color = $dataColor[ $dataColorCount % ( $#dataColor + 1 ) ]; $cell = CGI::span( { style => 'color:' . $color }, ' ' . $cell . ' ' ) unless $color =~ /^(|none)$/i; } $type = 'td' unless $type eq 'Y'; } ###if( $type eq 'th' ) my $isLastRow = ( $rowCount == $numberOfRows - 1 ); if ( $attr->{rowspan} ) { $isLastRow = ( ( $rowCount + ( $attr->{rowspan} - 1 ) ) == $numberOfRows - 1 ); } # CSS class name $attr->{class} = _appendFirstColumnCssClass( $attr->{class} ) if $colCount == 0; $attr->{class} = _appendLastRowCssClass( $attr->{class} ) if $isLastRow; $colCount++; next if ( $type eq 'Y' ); my $fn = 'CGI::' . $type; no strict 'refs'; $rowtext .= &$fn( $attr, " $cell " ); use strict 'refs'; } # foreach my $fcell ( @$row ) $text .= $currTablePre . CGI::Tr( { class => ( $rowCount % 2 ) ? 'twikiTableOdd' : 'twikiTableEven' }, $rowtext ) . "\n"; $rowCount++; $isHeaderRow = 1 if ( $headerCellCount == $colCount ); if ($isHeaderRow) { # reset data color count to start with first color after # each table heading $dataColorCount = 0; } else { $dataColorCount++; } } # foreach my $row ( @curTable ) $text .= $currTablePre . CGI::end_table() . "\n"; _setDefaults(); return $text; } sub handler { ### my ( $text, $removed ) = @_; unless ($TWiki::Plugins::TablePlugin::initialised) { $insideTABLE = 0; $tableCount = 0; $twoCol = 1; my $cgi = TWiki::Func::getCgiQuery(); return unless $cgi; # Extract and attach existing parameters my $plist = $cgi->query_string(); $plist =~ s/\;/\&/go; $plist =~ s/\&?sortcol.*up=[0-9]+\&?//go; $plist .= '&' if $plist; $url = $cgi->url . $cgi->path_info() . '?' . $plist; $url =~ s/\&/\&/go; $sortColFromUrl = $cgi->param('sortcol'); # zero based: 0 is first column $requestedTable = $cgi->param('table'); $up = $cgi->param('up'); $sortTablesInText = 0; $sortAttachments = 0; my $tmp = TWiki::Func::getPreferencesValue('TABLEPLUGIN_SORT'); if ( !$tmp || $tmp =~ /^all$/oi ) { $sortTablesInText = 1; $sortAttachments = 1; } elsif ( $tmp =~ /^attachments$/oi ) { $sortAttachments = 1; } $pluginAttrs = TWiki::Func::getPreferencesValue('TABLEPLUGIN_TABLEATTRIBUTES'); $prefsAttrs = TWiki::Func::getPreferencesValue('TABLEATTRIBUTES'); _setDefaults(); $TWiki::Plugins::TablePlugin::initialised = 1; } undef $initSort; $insideTABLE = 0; my $defaultSort = $sortAllTables; my $acceptable = $sortAllTables; my @lines = split( /\r?\n/, $_[0] ); for (@lines) { if (s/%TABLE(?:{(.*?)})?%/_parseParameters($1)/se) { $acceptable = 1; } elsif ( $acceptable && s/^(\s*)\|(.*\|\s*)$/_processTableRow($1,$2)/eo ) { $insideTABLE = 1; } elsif ($insideTABLE) { $_ = emitTable() . $_; $insideTABLE = 0; undef $initSort; $sortAllTables = $defaultSort; $acceptable = $defaultSort; } } $_[0] = join( "\n", @lines ); if ($insideTABLE) { $_[0] .= emitTable(); } } 1;