| 1 | # See bottom of file for license and copyright information |
|---|
| 2 | |
|---|
| 3 | use strict; |
|---|
| 4 | use warnings; |
|---|
| 5 | |
|---|
| 6 | package Foswiki::Plugins::TablePlugin::Core; |
|---|
| 7 | |
|---|
| 8 | use Foswiki::Func; |
|---|
| 9 | use Foswiki::Plugins::TablePlugin (); |
|---|
| 10 | use Foswiki::Time; |
|---|
| 11 | use Error qw(:try); |
|---|
| 12 | |
|---|
| 13 | my @curTable; |
|---|
| 14 | my $translationToken; |
|---|
| 15 | my $insideTABLE; |
|---|
| 16 | my $currTablePre; |
|---|
| 17 | my $didWriteDefaultStyle; |
|---|
| 18 | my $defaultAttrs; # to write generic table CSS |
|---|
| 19 | my $tableSpecificAttrs; # to write table specific table CSS |
|---|
| 20 | my $combinedTableAttrs; # default and specific table attributes |
|---|
| 21 | my $styles = {}; # hash of default and specific styles |
|---|
| 22 | my @messages = (); |
|---|
| 23 | |
|---|
| 24 | # not yet refactored: |
|---|
| 25 | my $tableCount; |
|---|
| 26 | my $sortCol; |
|---|
| 27 | my $MAX_SORT_COLS; |
|---|
| 28 | my $requestedTable; |
|---|
| 29 | my $up; |
|---|
| 30 | my $sortTablesInText; |
|---|
| 31 | my $sortAttachments; |
|---|
| 32 | my $sortColFromUrl; |
|---|
| 33 | my $url; |
|---|
| 34 | my $currentSortDirection; |
|---|
| 35 | my @rowspan; |
|---|
| 36 | |
|---|
| 37 | my $HEAD_ID_DEFAULT_STYLE = |
|---|
| 38 | 'TABLEPLUGIN_default'; # this name is part of the API, do not change |
|---|
| 39 | my $HEAD_ID_SPECIFIC_STYLE = |
|---|
| 40 | 'TABLEPLUGIN_specific'; # this name is part of the API, do not change |
|---|
| 41 | |
|---|
| 42 | my $PATTERN_TABLE = qr/%TABLE(?:{(.*?)})?%/; |
|---|
| 43 | my $URL_ICON = |
|---|
| 44 | Foswiki::Func::getPubUrlPath() . '/' |
|---|
| 45 | . $Foswiki::cfg{SystemWebName} |
|---|
| 46 | . '/DocumentGraphics/'; |
|---|
| 47 | my $GIF_TABLE_SORT_ASCENDING = CGI::img( |
|---|
| 48 | { |
|---|
| 49 | src => $URL_ICON . 'tablesortup.gif', |
|---|
| 50 | border => 0, |
|---|
| 51 | width => 11, |
|---|
| 52 | height => 13, |
|---|
| 53 | alt => 'Sorted ascending', |
|---|
| 54 | title => 'Sorted ascending' |
|---|
| 55 | } |
|---|
| 56 | ); |
|---|
| 57 | |
|---|
| 58 | my $GIF_TABLE_SORT_DESCENDING = CGI::img( |
|---|
| 59 | { |
|---|
| 60 | src => $URL_ICON . 'tablesortdown.gif', |
|---|
| 61 | border => 0, |
|---|
| 62 | width => 11, |
|---|
| 63 | height => 13, |
|---|
| 64 | alt => 'Sorted descending', |
|---|
| 65 | title => 'Sorted descending' |
|---|
| 66 | } |
|---|
| 67 | ); |
|---|
| 68 | |
|---|
| 69 | my $GIF_TABLE_SORT_BOTH = CGI::img( |
|---|
| 70 | { |
|---|
| 71 | src => $URL_ICON . 'tablesortdiamond.gif', |
|---|
| 72 | border => 0, |
|---|
| 73 | width => 11, |
|---|
| 74 | height => 13, |
|---|
| 75 | alt => 'Sort', |
|---|
| 76 | title => 'Sort' |
|---|
| 77 | } |
|---|
| 78 | ); |
|---|
| 79 | my $CHAR_SORT_ASCENDING = CGI::span( { class => 'tableSortIcon tableSortUp' }, |
|---|
| 80 | $GIF_TABLE_SORT_ASCENDING ); |
|---|
| 81 | my $CHAR_SORT_DESCENDING = |
|---|
| 82 | CGI::span( { class => 'tableSortIcon tableSortDown' }, |
|---|
| 83 | $GIF_TABLE_SORT_DESCENDING ); |
|---|
| 84 | my $CHAR_SORT_BOTH = |
|---|
| 85 | CGI::span( { class => 'tableSortIcon tableSortUp' }, $GIF_TABLE_SORT_BOTH ); |
|---|
| 86 | |
|---|
| 87 | my $SORT_DIRECTION = { |
|---|
| 88 | 'ASCENDING' => 0, |
|---|
| 89 | 'DESCENDING' => 1, |
|---|
| 90 | 'NONE' => 2, |
|---|
| 91 | }; |
|---|
| 92 | |
|---|
| 93 | my $PATTERN_ATTRIBUTE_SIZE = qr'([0-9]+)(px|%)*'o; |
|---|
| 94 | |
|---|
| 95 | my $TABLE_RULES = {}; |
|---|
| 96 | $TABLE_RULES->{all}->{TD} = $TABLE_RULES->{all}->{TH} = |
|---|
| 97 | $TABLE_RULES->{data_all}->{TD} = $TABLE_RULES->{header_all}->{TH} = |
|---|
| 98 | 'border-style:solid'; |
|---|
| 99 | $TABLE_RULES->{none}->{TD} = $TABLE_RULES->{none}->{TH} = |
|---|
| 100 | $TABLE_RULES->{data_none}->{TD} = $TABLE_RULES->{header_none}->{TH} = |
|---|
| 101 | 'border-style:none'; |
|---|
| 102 | $TABLE_RULES->{cols}->{TD} = $TABLE_RULES->{cols}->{TH} = |
|---|
| 103 | $TABLE_RULES->{data_cols}->{TD} = $TABLE_RULES->{header_cols}->{TH} = |
|---|
| 104 | 'border-style:none solid'; |
|---|
| 105 | $TABLE_RULES->{rows}->{TD} = $TABLE_RULES->{rows}->{TH} = |
|---|
| 106 | $TABLE_RULES->{data_rows}->{TD} = $TABLE_RULES->{header_rows}->{TH} = |
|---|
| 107 | 'border-style:solid none'; |
|---|
| 108 | $TABLE_RULES->{groups}->{TD} = 'border-style:none'; |
|---|
| 109 | $TABLE_RULES->{groups}->{TH} = 'border-style:solid none'; |
|---|
| 110 | |
|---|
| 111 | my $TABLE_FRAME = {}; |
|---|
| 112 | $TABLE_FRAME->{void} = 'border-style:none'; |
|---|
| 113 | $TABLE_FRAME->{above} = 'border-style:solid none none none'; |
|---|
| 114 | $TABLE_FRAME->{below} = 'border-style:none none solid none'; |
|---|
| 115 | $TABLE_FRAME->{lhs} = 'border-style:none none none solid'; |
|---|
| 116 | $TABLE_FRAME->{rhs} = 'border-style:none solid none none'; |
|---|
| 117 | $TABLE_FRAME->{hsides} = 'border-style:solid none solid none'; |
|---|
| 118 | $TABLE_FRAME->{vsides} = 'border-style:none solid none solid'; |
|---|
| 119 | $TABLE_FRAME->{box} = 'border-style:solid'; |
|---|
| 120 | $TABLE_FRAME->{border} = 'border-style:solid'; |
|---|
| 121 | |
|---|
| 122 | sub _init { |
|---|
| 123 | _debug("_init"); |
|---|
| 124 | $translationToken = "\0"; |
|---|
| 125 | |
|---|
| 126 | # the maximum number of columns we will handle |
|---|
| 127 | $MAX_SORT_COLS = 10000; |
|---|
| 128 | $didWriteDefaultStyle = 0; |
|---|
| 129 | $tableCount = 0; |
|---|
| 130 | $currTablePre = ''; |
|---|
| 131 | $combinedTableAttrs = {}; |
|---|
| 132 | $tableSpecificAttrs = {}; |
|---|
| 133 | } |
|---|
| 134 | |
|---|
| 135 | # called one time |
|---|
| 136 | sub _initDefaults { |
|---|
| 137 | _debug('_initDefaults'); |
|---|
| 138 | $defaultAttrs = { |
|---|
| 139 | headerrows => 0, |
|---|
| 140 | footerrows => 0, |
|---|
| 141 | sort => 1, |
|---|
| 142 | class => 'foswikiTable', |
|---|
| 143 | sortAllTables => $sortTablesInText, |
|---|
| 144 | }; |
|---|
| 145 | _parseDefaultAttributes( |
|---|
| 146 | %{Foswiki::Plugins::TablePlugin::pluginAttributes} ); |
|---|
| 147 | |
|---|
| 148 | $combinedTableAttrs = _mergeHashes( {}, $defaultAttrs ); |
|---|
| 149 | } |
|---|
| 150 | |
|---|
| 151 | sub _addDefaultStyles { |
|---|
| 152 | return if $Foswiki::Plugins::TablePlugin::writtenToHead; |
|---|
| 153 | $Foswiki::Plugins::TablePlugin::writtenToHead = 1; |
|---|
| 154 | |
|---|
| 155 | # create CSS styles tables in general |
|---|
| 156 | my ( $id, @styles ) = _createCssStyles( 1, $defaultAttrs ); |
|---|
| 157 | _addHeadStyles( $HEAD_ID_DEFAULT_STYLE, @styles ) if scalar @styles; |
|---|
| 158 | } |
|---|
| 159 | |
|---|
| 160 | sub _resetReusedVariables { |
|---|
| 161 | _debug('_resetReusedVariables'); |
|---|
| 162 | $currTablePre = ''; |
|---|
| 163 | $combinedTableAttrs = _mergeHashes( {}, $defaultAttrs ); |
|---|
| 164 | $tableSpecificAttrs = {}; |
|---|
| 165 | $sortCol = 0; |
|---|
| 166 | @messages = (); |
|---|
| 167 | } |
|---|
| 168 | |
|---|
| 169 | =pod |
|---|
| 170 | |
|---|
| 171 | =cut |
|---|
| 172 | |
|---|
| 173 | sub _storeAttribute { |
|---|
| 174 | my ( $inAttrName, $inValue, $inCollection ) = @_; |
|---|
| 175 | |
|---|
| 176 | if ( !$inCollection ) { |
|---|
| 177 | _debug('_storeAttribute -- missing inCollection!'); |
|---|
| 178 | return; |
|---|
| 179 | } |
|---|
| 180 | return if !defined $inValue; |
|---|
| 181 | return if !defined $inAttrName || $inAttrName eq ''; |
|---|
| 182 | $inCollection->{$inAttrName} = $inValue; |
|---|
| 183 | } |
|---|
| 184 | |
|---|
| 185 | =pod |
|---|
| 186 | |
|---|
| 187 | =cut |
|---|
| 188 | |
|---|
| 189 | sub _parseDefaultAttributes { |
|---|
| 190 | my (%params) = @_; |
|---|
| 191 | |
|---|
| 192 | _debug('_parseDefaultAttributes'); |
|---|
| 193 | |
|---|
| 194 | _parseAttributes( 0, $defaultAttrs, \%params ); |
|---|
| 195 | } |
|---|
| 196 | |
|---|
| 197 | =pod |
|---|
| 198 | |
|---|
| 199 | =cut |
|---|
| 200 | |
|---|
| 201 | sub _parseTableSpecificTableAttributes { |
|---|
| 202 | my (%params) = @_; |
|---|
| 203 | |
|---|
| 204 | _debug('_parseTableSpecificTableAttributes'); |
|---|
| 205 | |
|---|
| 206 | _parseAttributes( 1, $tableSpecificAttrs, \%params ); |
|---|
| 207 | |
|---|
| 208 | # remove default values from hash |
|---|
| 209 | while ( my ( $key, $value ) = each %{$tableSpecificAttrs} ) { |
|---|
| 210 | delete $tableSpecificAttrs->{$key} |
|---|
| 211 | if $defaultAttrs->{$key} && $value eq $defaultAttrs->{$key}; |
|---|
| 212 | } |
|---|
| 213 | $combinedTableAttrs = |
|---|
| 214 | _mergeHashes( $combinedTableAttrs, $tableSpecificAttrs ); |
|---|
| 215 | _debugData( 'combinedTableAttrs', $combinedTableAttrs ); |
|---|
| 216 | |
|---|
| 217 | # create CSS styles for this table only |
|---|
| 218 | my ( $id, @styles ) = _createCssStyles( 0, $tableSpecificAttrs ); |
|---|
| 219 | _debugData( "after _createCssStyles, id=$id; styles", \@styles ); |
|---|
| 220 | |
|---|
| 221 | _addHeadStyles( $id, @styles ) if scalar @styles; |
|---|
| 222 | |
|---|
| 223 | return $currTablePre . '<nop>'; |
|---|
| 224 | } |
|---|
| 225 | |
|---|
| 226 | =pod |
|---|
| 227 | |
|---|
| 228 | =cut |
|---|
| 229 | |
|---|
| 230 | sub _parseAttributes { |
|---|
| 231 | my ( $isTableSpecific, $inCollection, $inParams ) = @_; |
|---|
| 232 | |
|---|
| 233 | _debugData( "isTableSpecific=$isTableSpecific; _parseAttributes=", |
|---|
| 234 | $inParams ); |
|---|
| 235 | |
|---|
| 236 | # include topic to read definitions |
|---|
| 237 | if ( $inParams->{include} ) { |
|---|
| 238 | my ( $includeParams, $message ) = |
|---|
| 239 | _getIncludeParams( $inParams->{include} ); |
|---|
| 240 | |
|---|
| 241 | if ($includeParams) { |
|---|
| 242 | $inParams = $includeParams; |
|---|
| 243 | } |
|---|
| 244 | if ($message) { |
|---|
| 245 | push( @messages, $message ); |
|---|
| 246 | } |
|---|
| 247 | } |
|---|
| 248 | |
|---|
| 249 | # table attributes |
|---|
| 250 | # some will be used for css styling as well |
|---|
| 251 | |
|---|
| 252 | _storeAttribute( 'generateInlineMarkup', |
|---|
| 253 | Foswiki::Func::isTrue( $inParams->{inlinemarkup} ), |
|---|
| 254 | $inCollection ) |
|---|
| 255 | if defined $inParams->{inlinemarkup}; |
|---|
| 256 | |
|---|
| 257 | # sort attributes |
|---|
| 258 | if ( defined $inParams->{sort} ) { |
|---|
| 259 | my $sort = Foswiki::Func::isTrue( $inParams->{sort} ); |
|---|
| 260 | _storeAttribute( 'sort', $sort, $inCollection ); |
|---|
| 261 | _storeAttribute( 'sortAllTables', $sort, $inCollection ); |
|---|
| 262 | } |
|---|
| 263 | if ( defined( $inParams->{initsort} ) |
|---|
| 264 | and int( $inParams->{initsort} ) > 0 ) |
|---|
| 265 | { |
|---|
| 266 | _storeAttribute( 'initSort', $inParams->{initsort}, $inCollection ); |
|---|
| 267 | |
|---|
| 268 | # override sort attribute: we are sorting after all |
|---|
| 269 | _storeAttribute( 'sort', 1, $inCollection ); |
|---|
| 270 | } |
|---|
| 271 | |
|---|
| 272 | if ( $inParams->{initdirection} ) { |
|---|
| 273 | _storeAttribute( 'initDirection', $SORT_DIRECTION->{'ASCENDING'}, |
|---|
| 274 | $inCollection ) |
|---|
| 275 | if $inParams->{initdirection} =~ /^down$/i; |
|---|
| 276 | _storeAttribute( 'initDirection', $SORT_DIRECTION->{'DESCENDING'}, |
|---|
| 277 | $inCollection ) |
|---|
| 278 | if $inParams->{initdirection} =~ /^up$/i; |
|---|
| 279 | } |
|---|
| 280 | |
|---|
| 281 | # If EditTablePlugin is installed and we are editing a table, |
|---|
| 282 | # the CGI parameter 'sort' is defined as "off" to disable all |
|---|
| 283 | # header sorting ((Item5135) |
|---|
| 284 | my $cgi = Foswiki::Func::getCgiQuery(); |
|---|
| 285 | my $urlParamSort = $cgi->param('sort'); |
|---|
| 286 | if ( $urlParamSort && $urlParamSort =~ /^off$/oi ) { |
|---|
| 287 | delete $inCollection->{sortAllTables}; |
|---|
| 288 | } |
|---|
| 289 | |
|---|
| 290 | # If EditTablePlugin is installed and we are editing a table, the |
|---|
| 291 | # 'disableallsort' TABLE parameter is added to disable initsort and header |
|---|
| 292 | # sorting in the table that is being edited. (Item5135) |
|---|
| 293 | if ( Foswiki::Func::isTrue( $inParams->{disableallsort} ) ) { |
|---|
| 294 | $inCollection->{sortAllTables} = 0; |
|---|
| 295 | delete $inCollection->{initSort}; |
|---|
| 296 | } |
|---|
| 297 | |
|---|
| 298 | if ($isTableSpecific) { |
|---|
| 299 | |
|---|
| 300 | _storeAttribute( 'summary', $inParams->{summary}, $inCollection ); |
|---|
| 301 | my $id = |
|---|
| 302 | defined $inParams->{id} |
|---|
| 303 | ? $inParams->{id} |
|---|
| 304 | : 'table' |
|---|
| 305 | . $Foswiki::Plugins::TablePlugin::topic |
|---|
| 306 | . ( $tableCount + 1 ); |
|---|
| 307 | _storeAttribute( 'id', $id, $inCollection ); |
|---|
| 308 | _storeAttribute( 'headerrows', $inParams->{headerrows}, $inCollection ); |
|---|
| 309 | _storeAttribute( 'footerrows', $inParams->{footerrows}, $inCollection ); |
|---|
| 310 | } |
|---|
| 311 | _storeAttribute( 'border', $inParams->{tableborder}, $inCollection ); |
|---|
| 312 | _storeAttribute( 'tableBorderColor', $inParams->{tablebordercolor}, |
|---|
| 313 | $inCollection ); |
|---|
| 314 | _storeAttribute( 'cellpadding', $inParams->{cellpadding}, $inCollection ); |
|---|
| 315 | _storeAttribute( 'cellspacing', $inParams->{cellspacing}, $inCollection ); |
|---|
| 316 | _storeAttribute( 'frame', $inParams->{tableframe}, $inCollection ); |
|---|
| 317 | |
|---|
| 318 | # tablerules css settings |
|---|
| 319 | my @tableRulesList = (); |
|---|
| 320 | if ( $inParams->{tablerules} ) { |
|---|
| 321 | |
|---|
| 322 | # store tablerules as array, so that headerrules and datarules |
|---|
| 323 | # can be appended to that list |
|---|
| 324 | my $param = _cleanParamValue( $inParams->{tablerules} ); |
|---|
| 325 | if ($param) { |
|---|
| 326 | push( @tableRulesList, $param ); |
|---|
| 327 | } |
|---|
| 328 | } |
|---|
| 329 | if ( $inParams->{headerrules} ) { |
|---|
| 330 | my $param = _cleanParamValue( $inParams->{headerrules} ); |
|---|
| 331 | if ($param) { |
|---|
| 332 | $param = "header_$param"; |
|---|
| 333 | push( @tableRulesList, $param ); |
|---|
| 334 | } |
|---|
| 335 | } |
|---|
| 336 | if ( $inParams->{datarules} ) { |
|---|
| 337 | my $param = _cleanParamValue( $inParams->{datarules} ); |
|---|
| 338 | if ($param) { |
|---|
| 339 | $param = "data_$param"; |
|---|
| 340 | push( @tableRulesList, $param ); |
|---|
| 341 | } |
|---|
| 342 | } |
|---|
| 343 | $inCollection->{tableRules} = \@tableRulesList if scalar @tableRulesList; |
|---|
| 344 | |
|---|
| 345 | # use 'rules' as table attribute only (not to define css styles) |
|---|
| 346 | # but set to |
|---|
| 347 | my $rules = |
|---|
| 348 | ( defined $inParams->{headerrules} || defined $inParams->{datarules} ) |
|---|
| 349 | ? 'none' |
|---|
| 350 | : $inParams->{tablerules}; |
|---|
| 351 | _storeAttribute( 'rules', $rules, $inCollection ); |
|---|
| 352 | |
|---|
| 353 | _storeAttribute( 'width', $inParams->{tablewidth}, $inCollection ); |
|---|
| 354 | |
|---|
| 355 | # css attributes |
|---|
| 356 | _storeAttribute( 'headerColor', $inParams->{headercolor}, $inCollection ); |
|---|
| 357 | _storeAttribute( 'headerBg', $inParams->{headerbg}, $inCollection ); |
|---|
| 358 | _storeAttribute( 'cellBorder', $inParams->{cellborder}, $inCollection ); |
|---|
| 359 | _storeAttribute( 'headerAlignListRef', |
|---|
| 360 | _arrayRefFromParam( $inParams->{headeralign} ), |
|---|
| 361 | $inCollection ); |
|---|
| 362 | _storeAttribute( 'dataAlignListRef', |
|---|
| 363 | _arrayRefFromParam( $inParams->{dataalign} ), |
|---|
| 364 | $inCollection ); |
|---|
| 365 | _storeAttribute( 'columnWidthsListRef', |
|---|
| 366 | _arrayRefFromParam( $inParams->{columnwidths} ), |
|---|
| 367 | $inCollection ); |
|---|
| 368 | _storeAttribute( 'vAlign', $inParams->{valign} || 'top', $inCollection ); |
|---|
| 369 | _storeAttribute( 'dataVAlign', $inParams->{datavalign}, $inCollection ); |
|---|
| 370 | _storeAttribute( 'headerVAlign', $inParams->{headervalign}, $inCollection ); |
|---|
| 371 | _storeAttribute( 'headerBgSorted', |
|---|
| 372 | $inParams->{headerbgsorted} || $inParams->{headerbg}, |
|---|
| 373 | $inCollection ); |
|---|
| 374 | _storeAttribute( 'dataBgListRef', _arrayRefFromParam( $inParams->{databg} ), |
|---|
| 375 | $inCollection ); |
|---|
| 376 | _storeAttribute( |
|---|
| 377 | 'dataBgSortedListRef', |
|---|
| 378 | _arrayRefFromParam( $inParams->{databgsorted} || $inParams->{databg} ), |
|---|
| 379 | $inCollection |
|---|
| 380 | ); |
|---|
| 381 | _storeAttribute( 'dataColorListRef', |
|---|
| 382 | _arrayRefFromParam( $inParams->{datacolor} ), |
|---|
| 383 | $inCollection ); |
|---|
| 384 | _storeAttribute( 'tableCaption', $inParams->{caption}, $inCollection ); |
|---|
| 385 | |
|---|
| 386 | # remove empty attributes |
|---|
| 387 | while ( my ( $key, $value ) = each %{$inCollection} ) { |
|---|
| 388 | delete $inCollection->{$key} if !defined $value || $value eq ''; |
|---|
| 389 | } |
|---|
| 390 | |
|---|
| 391 | _debugData( '_parseAttributes result:', $inCollection ); |
|---|
| 392 | } |
|---|
| 393 | |
|---|
| 394 | =pod |
|---|
| 395 | |
|---|
| 396 | _getIncludeParams( $includeTopic ) -> \%params |
|---|
| 397 | |
|---|
| 398 | From $includeTopic read the first TABLE tag and return its parameters. |
|---|
| 399 | |
|---|
| 400 | =cut |
|---|
| 401 | |
|---|
| 402 | sub _getIncludeParams { |
|---|
| 403 | my ($inIncludeTopic) = @_; |
|---|
| 404 | |
|---|
| 405 | my ( $includeWeb, $includeTopic ) = |
|---|
| 406 | Foswiki::Func::normalizeWebTopicName( $Foswiki::Plugins::TablePlugin::web, |
|---|
| 407 | $inIncludeTopic ); |
|---|
| 408 | |
|---|
| 409 | _debug("_getIncludeParams:$inIncludeTopic"); |
|---|
| 410 | _debug("\t includeTopic=$includeTopic") if $includeTopic; |
|---|
| 411 | |
|---|
| 412 | if ( !Foswiki::Func::topicExists( $includeWeb, $includeTopic ) ) { |
|---|
| 413 | _debug("TablePlugin: included topic $inIncludeTopic does not exist."); |
|---|
| 414 | return ( undef, |
|---|
| 415 | '%MAKETEXT{"Warning: \'include\' topic <nop>[_1] does not exist!" args="' |
|---|
| 416 | . "$includeWeb.$includeTopic" |
|---|
| 417 | . '"}%' ); |
|---|
| 418 | } |
|---|
| 419 | else { |
|---|
| 420 | |
|---|
| 421 | my $text = Foswiki::Func::readTopicText( $includeWeb, $includeTopic ); |
|---|
| 422 | |
|---|
| 423 | if ( $text =~ m/$PATTERN_TABLE/s ) { |
|---|
| 424 | _debug("\t PATTERN_TABLE=$PATTERN_TABLE; 1=$1"); |
|---|
| 425 | my $paramString = $1; |
|---|
| 426 | |
|---|
| 427 | if ( $includeWeb ne $Foswiki::Plugins::TablePlugin::web |
|---|
| 428 | || $includeTopic ne $Foswiki::Plugins::TablePlugin::topic ) |
|---|
| 429 | { |
|---|
| 430 | |
|---|
| 431 | # expand common vars, except oneself to prevent recursion |
|---|
| 432 | $paramString = |
|---|
| 433 | Foswiki::Func::expandCommonVariables( $paramString, |
|---|
| 434 | $includeTopic, $includeWeb ); |
|---|
| 435 | } |
|---|
| 436 | my %params = Foswiki::Func::extractParameters($paramString); |
|---|
| 437 | return ( \%params, undef ); |
|---|
| 438 | } |
|---|
| 439 | else { |
|---|
| 440 | return ( undef, |
|---|
| 441 | '%MAKETEXT{"Warning: table definition in \'include\' topic [_1] does not exist!" args="' |
|---|
| 442 | . "$includeWeb.$includeTopic" |
|---|
| 443 | . '"}%' ); |
|---|
| 444 | } |
|---|
| 445 | } |
|---|
| 446 | } |
|---|
| 447 | |
|---|
| 448 | =pod |
|---|
| 449 | |
|---|
| 450 | _convertStringToDate ( $text ) -> $number |
|---|
| 451 | |
|---|
| 452 | Convert text to number if syntactically possible, otherwise return undef. |
|---|
| 453 | Assumes that the text has been stripped from HTML markup. |
|---|
| 454 | |
|---|
| 455 | =cut |
|---|
| 456 | |
|---|
| 457 | sub _convertStringToDate { |
|---|
| 458 | my ($text) = @_; |
|---|
| 459 | |
|---|
| 460 | return undef if !defined $text; |
|---|
| 461 | return undef if $text eq ''; |
|---|
| 462 | return undef if ( $text =~ /^\s*$/ ); |
|---|
| 463 | |
|---|
| 464 | my $date = undef; |
|---|
| 465 | |
|---|
| 466 | if ( $text =~ /^\s*-?[0-9]+(\.[0-9])*\s*$/ ) { |
|---|
| 467 | _debug("\t this is a number"); |
|---|
| 468 | } |
|---|
| 469 | else { |
|---|
| 470 | try { |
|---|
| 471 | $date = Foswiki::Time::parseTime($text); |
|---|
| 472 | _debug("\t is a date"); |
|---|
| 473 | } |
|---|
| 474 | catch Error::Simple with { |
|---|
| 475 | |
|---|
| 476 | # nope, wasn't a date |
|---|
| 477 | _debug("\t $text is not a date"); |
|---|
| 478 | }; |
|---|
| 479 | } |
|---|
| 480 | |
|---|
| 481 | return $date; |
|---|
| 482 | } |
|---|
| 483 | |
|---|
| 484 | =pod |
|---|
| 485 | |
|---|
| 486 | _convertStringToNumber ( $text ) -> $number |
|---|
| 487 | |
|---|
| 488 | Convert text to number if syntactically possible, otherwise return undef. |
|---|
| 489 | Assumes that the text has been stripped from HTML markup. |
|---|
| 490 | |
|---|
| 491 | =cut |
|---|
| 492 | |
|---|
| 493 | sub _convertStringToNumber { |
|---|
| 494 | my ($text) = @_; |
|---|
| 495 | |
|---|
| 496 | return undef if !defined $text; |
|---|
| 497 | return undef if $text eq ''; |
|---|
| 498 | return undef if ( $text =~ /^\s*$/ ); |
|---|
| 499 | |
|---|
| 500 | # very course testing on IP (could in fact be anything with n.n. syntax |
|---|
| 501 | if ( |
|---|
| 502 | $text =~ m/ |
|---|
| 503 | ^ |
|---|
| 504 | \s* # any space |
|---|
| 505 | (?: # don't need to capture |
|---|
| 506 | [0-9]+ # digits |
|---|
| 507 | \. # dot |
|---|
| 508 | ) # |
|---|
| 509 | {2,} # repeat more than once: exclude decimal numbers |
|---|
| 510 | .*? # any string |
|---|
| 511 | $ |
|---|
| 512 | /x |
|---|
| 513 | ) |
|---|
| 514 | { |
|---|
| 515 | _debug("\t $text looks like an IP address, or something similar"); |
|---|
| 516 | |
|---|
| 517 | # should be sorted by text |
|---|
| 518 | return undef; |
|---|
| 519 | } |
|---|
| 520 | |
|---|
| 521 | if ( |
|---|
| 522 | $text =~ m/ |
|---|
| 523 | ^ |
|---|
| 524 | \s* # any space |
|---|
| 525 | ( # |
|---|
| 526 | -* # possible minus |
|---|
| 527 | [0-9]+ # digits |
|---|
| 528 | \.* # possible decimal |
|---|
| 529 | [0-9]* # possible fracture digits |
|---|
| 530 | ) # end capture of number |
|---|
| 531 | .*$ # any string |
|---|
| 532 | /x |
|---|
| 533 | ) |
|---|
| 534 | { |
|---|
| 535 | |
|---|
| 536 | _debug("\t $1 is a number"); |
|---|
| 537 | |
|---|
| 538 | # make sure to return a number, not a string |
|---|
| 539 | return $1 * 1.0; |
|---|
| 540 | } |
|---|
| 541 | return undef; |
|---|
| 542 | } |
|---|
| 543 | |
|---|
| 544 | sub _processTableRow { |
|---|
| 545 | my ( $thePre, $theRow ) = @_; |
|---|
| 546 | |
|---|
| 547 | $currTablePre = $thePre || ''; |
|---|
| 548 | my $span = 0; |
|---|
| 549 | my $l1 = 0; |
|---|
| 550 | my $l2 = 0; |
|---|
| 551 | |
|---|
| 552 | if ( !$insideTABLE ) { |
|---|
| 553 | @curTable = (); |
|---|
| 554 | @rowspan = (); |
|---|
| 555 | |
|---|
| 556 | $tableCount++; |
|---|
| 557 | $currentSortDirection = $SORT_DIRECTION->{'NONE'}; |
|---|
| 558 | |
|---|
| 559 | if ( defined $requestedTable |
|---|
| 560 | && $requestedTable == $tableCount |
|---|
| 561 | && defined $sortColFromUrl ) |
|---|
| 562 | { |
|---|
| 563 | $sortCol = $sortColFromUrl; |
|---|
| 564 | $sortCol = 0 unless ( $sortCol =~ m/^[0-9]+$/ ); |
|---|
| 565 | $sortCol = $MAX_SORT_COLS if ( $sortCol > $MAX_SORT_COLS ); |
|---|
| 566 | $currentSortDirection = _getCurrentSortDirection($up); |
|---|
| 567 | } |
|---|
| 568 | elsif ( defined $combinedTableAttrs->{initSort} ) { |
|---|
| 569 | $sortCol = $combinedTableAttrs->{initSort} - 1; |
|---|
| 570 | $sortCol = $MAX_SORT_COLS if ( $sortCol > $MAX_SORT_COLS ); |
|---|
| 571 | $currentSortDirection = |
|---|
| 572 | _getCurrentSortDirection( $combinedTableAttrs->{initDirection} ); |
|---|
| 573 | } |
|---|
| 574 | } |
|---|
| 575 | |
|---|
| 576 | $theRow =~ s/\t/ /go; # change tabs to space |
|---|
| 577 | $theRow =~ s/\s*$//o; # remove trailing spaces |
|---|
| 578 | $theRow =~ |
|---|
| 579 | s/(\|\|+)/'colspan'.$translationToken.length($1)."\|"/geo; # calc COLSPAN |
|---|
| 580 | my $colCount = 0; |
|---|
| 581 | my @row = (); |
|---|
| 582 | $span = 0; |
|---|
| 583 | my $value = ''; |
|---|
| 584 | |
|---|
| 585 | foreach ( split( /\|/, $theRow ) ) { |
|---|
| 586 | my $attr = {}; |
|---|
| 587 | $span = 1; |
|---|
| 588 | |
|---|
| 589 | #AS 25-5-01 Fix to avoid matching also single columns |
|---|
| 590 | if (s/colspan$translationToken([0-9]+)//) { |
|---|
| 591 | $span = $1; |
|---|
| 592 | $attr->{colspan} = $span; |
|---|
| 593 | } |
|---|
| 594 | s/^\s+$/ /o; |
|---|
| 595 | ( $l1, $l2 ) = ( 0, 0 ); |
|---|
| 596 | if (/^(\s*).*?(\s*)$/) { |
|---|
| 597 | $l1 = length($1); |
|---|
| 598 | $l2 = length($2); |
|---|
| 599 | } |
|---|
| 600 | if ( $l1 >= 2 ) { |
|---|
| 601 | if ( $l2 <= 1 ) { |
|---|
| 602 | $attr->{align} = 'right'; |
|---|
| 603 | } |
|---|
| 604 | else { |
|---|
| 605 | $attr->{align} = 'center'; |
|---|
| 606 | } |
|---|
| 607 | } |
|---|
| 608 | if ( $span <= 2 ) { |
|---|
| 609 | $attr->{class} = |
|---|
| 610 | _appendColNumberCssClass( $attr->{class}, $colCount ); |
|---|
| 611 | } |
|---|
| 612 | |
|---|
| 613 | # html attribute: (column) width |
|---|
| 614 | if ( $combinedTableAttrs->{generateInlineMarkup} |
|---|
| 615 | && defined $combinedTableAttrs->{columnWidthsListRef} ) |
|---|
| 616 | { |
|---|
| 617 | my @columnWidths = @{ $combinedTableAttrs->{columnWidthsListRef} }; |
|---|
| 618 | if ( defined $columnWidths[$colCount] |
|---|
| 619 | && $columnWidths[$colCount] |
|---|
| 620 | && $span <= 2 ) |
|---|
| 621 | { |
|---|
| 622 | $attr->{width} = $columnWidths[$colCount]; |
|---|
| 623 | } |
|---|
| 624 | } |
|---|
| 625 | |
|---|
| 626 | # END html attribute |
|---|
| 627 | |
|---|
| 628 | if (/^(\s|<[^>]*>)*\^(\s|<[^>]*>)*$/) { # row span above |
|---|
| 629 | $rowspan[$colCount]++; |
|---|
| 630 | push @row, { text => $value, type => 'Y' }; |
|---|
| 631 | } |
|---|
| 632 | else { |
|---|
| 633 | for ( my $col = $colCount ; $col < ( $colCount + $span ) ; $col++ ) |
|---|
| 634 | { |
|---|
| 635 | if ( defined( $rowspan[$col] ) && $rowspan[$col] ) { |
|---|
| 636 | my $nRows = scalar(@curTable); |
|---|
| 637 | my $rspan = $rowspan[$col] + 1; |
|---|
| 638 | if ( $rspan > 1 ) { |
|---|
| 639 | $curTable[ $nRows - $rspan ][$col]->{attrs}->{rowspan} = |
|---|
| 640 | $rspan; |
|---|
| 641 | } |
|---|
| 642 | undef( $rowspan[$col] ); |
|---|
| 643 | } |
|---|
| 644 | } |
|---|
| 645 | |
|---|
| 646 | if ( |
|---|
| 647 | ( |
|---|
| 648 | ( |
|---|
| 649 | defined $requestedTable |
|---|
| 650 | && $requestedTable == $tableCount |
|---|
| 651 | ) |
|---|
| 652 | || defined $combinedTableAttrs->{initSort} |
|---|
| 653 | ) |
|---|
| 654 | && defined $sortCol |
|---|
| 655 | && $colCount == $sortCol |
|---|
| 656 | ) |
|---|
| 657 | { |
|---|
| 658 | |
|---|
| 659 | # CSS class name |
|---|
| 660 | if ( $currentSortDirection == $SORT_DIRECTION->{'ASCENDING'} ) { |
|---|
| 661 | $attr->{class} = |
|---|
| 662 | _appendSortedAscendingCssClass( $attr->{class} ); |
|---|
| 663 | } |
|---|
| 664 | if ( $currentSortDirection == $SORT_DIRECTION->{'DESCENDING'} ) |
|---|
| 665 | { |
|---|
| 666 | $attr->{class} = |
|---|
| 667 | _appendSortedDescendingCssClass( $attr->{class} ); |
|---|
| 668 | } |
|---|
| 669 | } |
|---|
| 670 | |
|---|
| 671 | my $type = ''; |
|---|
| 672 | if (/^\s*\*(.*)\*\s*$/) { |
|---|
| 673 | $value = $1; |
|---|
| 674 | $type = 'th'; |
|---|
| 675 | |
|---|
| 676 | # html attribute: align |
|---|
| 677 | if ( $combinedTableAttrs->{generateInlineMarkup} |
|---|
| 678 | && defined $combinedTableAttrs->{headerAlignListRef} ) |
|---|
| 679 | { |
|---|
| 680 | my @headerAlign = |
|---|
| 681 | @{ $combinedTableAttrs->{headerAlignListRef} }; |
|---|
| 682 | if (@headerAlign) { |
|---|
| 683 | my $align = |
|---|
| 684 | @headerAlign[ $colCount % ( $#headerAlign + 1 ) ]; |
|---|
| 685 | $attr->{align} = $align; |
|---|
| 686 | } |
|---|
| 687 | } |
|---|
| 688 | |
|---|
| 689 | # END html attribute |
|---|
| 690 | |
|---|
| 691 | # html attribute: valign |
|---|
| 692 | if ( $combinedTableAttrs->{generateInlineMarkup} ) { |
|---|
| 693 | if ( defined $combinedTableAttrs->{headerVAlign} ) { |
|---|
| 694 | $attr->{valign} = $combinedTableAttrs->{headerVAlign}; |
|---|
| 695 | } |
|---|
| 696 | elsif ( defined $combinedTableAttrs->{vAlign} ) { |
|---|
| 697 | $attr->{valign} = $combinedTableAttrs->{vAlign}; |
|---|
| 698 | } |
|---|
| 699 | } |
|---|
| 700 | |
|---|
| 701 | # END html attribute |
|---|
| 702 | } |
|---|
| 703 | else { |
|---|
| 704 | if (/^\s*(.*?)\s*$/) { # strip white spaces |
|---|
| 705 | $_ = $1; |
|---|
| 706 | } |
|---|
| 707 | $value = $_; |
|---|
| 708 | $type = 'td'; |
|---|
| 709 | |
|---|
| 710 | # html attribute: align |
|---|
| 711 | if ( $combinedTableAttrs->{generateInlineMarkup} |
|---|
| 712 | && defined $combinedTableAttrs->{dataAlignListRef} ) |
|---|
| 713 | { |
|---|
| 714 | my @dataAlign = |
|---|
| 715 | @{ $combinedTableAttrs->{dataAlignListRef} }; |
|---|
| 716 | if (@dataAlign) { |
|---|
| 717 | my $align = |
|---|
| 718 | @dataAlign[ $colCount % ( $#dataAlign + 1 ) ]; |
|---|
| 719 | $attr->{align} = $align; |
|---|
| 720 | } |
|---|
| 721 | } |
|---|
| 722 | |
|---|
| 723 | # END html attribute |
|---|
| 724 | |
|---|
| 725 | # html attribute: valign |
|---|
| 726 | if ( $combinedTableAttrs->{generateInlineMarkup} ) { |
|---|
| 727 | if ( defined $combinedTableAttrs->{dataVAlign} ) { |
|---|
| 728 | $attr->{valign} = $combinedTableAttrs->{dataVAlign}; |
|---|
| 729 | } |
|---|
| 730 | elsif ( defined $combinedTableAttrs->{vAlign} ) { |
|---|
| 731 | $attr->{valign} = $combinedTableAttrs->{vAlign}; |
|---|
| 732 | } |
|---|
| 733 | } |
|---|
| 734 | |
|---|
| 735 | # END html attribute |
|---|
| 736 | } |
|---|
| 737 | |
|---|
| 738 | push @row, { text => $value, attrs => $attr, type => $type }; |
|---|
| 739 | } |
|---|
| 740 | while ( $span > 1 ) { |
|---|
| 741 | push @row, { text => $value, type => 'X' }; |
|---|
| 742 | $colCount++; |
|---|
| 743 | $span--; |
|---|
| 744 | } |
|---|
| 745 | $colCount++; |
|---|
| 746 | } |
|---|
| 747 | push @curTable, \@row; |
|---|
| 748 | return $currTablePre |
|---|
| 749 | . '<nop>'; # Avoid Foswiki converting empty lines to new paras |
|---|
| 750 | } |
|---|
| 751 | |
|---|
| 752 | sub _headerRowCount { |
|---|
| 753 | my ($table) = @_; |
|---|
| 754 | |
|---|
| 755 | my $headerCount = 0; |
|---|
| 756 | my $footerCount = 0; |
|---|
| 757 | my $endheader = 0; |
|---|
| 758 | |
|---|
| 759 | # All cells in header are headings? |
|---|
| 760 | foreach my $row (@$table) { |
|---|
| 761 | my $isHeader = 1; |
|---|
| 762 | foreach my $cell (@$row) { |
|---|
| 763 | if ( $cell->{type} ne 'th' ) { |
|---|
| 764 | $isHeader = 0; |
|---|
| 765 | $endheader = 1; |
|---|
| 766 | $footerCount = 0 if $footerCount; |
|---|
| 767 | } |
|---|
| 768 | } |
|---|
| 769 | unless ($endheader) { |
|---|
| 770 | $headerCount++ if $isHeader; |
|---|
| 771 | } |
|---|
| 772 | else { |
|---|
| 773 | $footerCount++ if $isHeader; |
|---|
| 774 | } |
|---|
| 775 | } |
|---|
| 776 | |
|---|
| 777 | # Some cells came after the footer - so there isn't one. |
|---|
| 778 | $footerCount = 0 if ( $endheader > 1 ); |
|---|
| 779 | |
|---|
| 780 | return ( $headerCount, $footerCount ); |
|---|
| 781 | } |
|---|
| 782 | |
|---|
| 783 | =pod |
|---|
| 784 | |
|---|
| 785 | _setSortTypeForCells ( $col, \@table ) |
|---|
| 786 | |
|---|
| 787 | Sets a sort key for each cell. |
|---|
| 788 | |
|---|
| 789 | =cut |
|---|
| 790 | |
|---|
| 791 | sub _setSortTypeForCells { |
|---|
| 792 | my ( $col, $table ) = @_; |
|---|
| 793 | |
|---|
| 794 | foreach my $row ( @{$table} ) { |
|---|
| 795 | |
|---|
| 796 | my $rowText = _stripHtml( $row->[$col]->{text} ); |
|---|
| 797 | |
|---|
| 798 | my $num = _convertStringToNumber($rowText); |
|---|
| 799 | my $date = _convertStringToDate($rowText); |
|---|
| 800 | |
|---|
| 801 | $row->[$col]->{sortText} = ''; |
|---|
| 802 | $row->[$col]->{number} = 0; |
|---|
| 803 | $row->[$col]->{dateString} = ''; |
|---|
| 804 | |
|---|
| 805 | if ( defined $date ) { |
|---|
| 806 | |
|---|
| 807 | # date has just converted to a number |
|---|
| 808 | $row->[$col]->{number} = $date; |
|---|
| 809 | |
|---|
| 810 | # add dateString value in case dates are equal |
|---|
| 811 | $row->[$col]->{dateString} = $rowText; |
|---|
| 812 | } |
|---|
| 813 | elsif ( defined $num ) { |
|---|
| 814 | $row->[$col]->{number} = $num; |
|---|
| 815 | |
|---|
| 816 | # when sorting mixed numbers and text, make the text sort value as low as possible |
|---|
| 817 | $row->[$col]->{sortText} = ' '; |
|---|
| 818 | } |
|---|
| 819 | else { |
|---|
| 820 | $row->[$col]->{sortText} = lc $rowText; |
|---|
| 821 | } |
|---|
| 822 | |
|---|
| 823 | } |
|---|
| 824 | } |
|---|
| 825 | |
|---|
| 826 | # Remove HTML from text so it can be sorted |
|---|
| 827 | sub _stripHtml { |
|---|
| 828 | my ($text) = @_; |
|---|
| 829 | |
|---|
| 830 | return undef if !defined $text; |
|---|
| 831 | $text =~ |
|---|
| 832 | s/\[\[[^\]]+\]\[([^\]]+)\]\]/$1/go; # extract label from [[...][...]] link |
|---|
| 833 | |
|---|
| 834 | my $orgtext = |
|---|
| 835 | $text; # in case we will have removed all contents with stripping html |
|---|
| 836 | $text =~ s/<[^>]+>//go; # strip HTML |
|---|
| 837 | $text =~ s/\ / /go; |
|---|
| 838 | $text = _getImageTextForSorting($orgtext) if ( $text eq '' ); |
|---|
| 839 | $text =~ s/[\[\]\*\|=_\&\<\>]/ /g; # remove Wiki formatting chars |
|---|
| 840 | $text =~ s/^ *//go; # strip leading space space |
|---|
| 841 | |
|---|
| 842 | return $text; |
|---|
| 843 | } |
|---|
| 844 | |
|---|
| 845 | =pod |
|---|
| 846 | |
|---|
| 847 | Retrieve text data from an image html tag to be used for sorting. |
|---|
| 848 | First try the alt tag string. If not available, return the url string. |
|---|
| 849 | If not available, return the original string. |
|---|
| 850 | |
|---|
| 851 | =cut |
|---|
| 852 | |
|---|
| 853 | sub _getImageTextForSorting { |
|---|
| 854 | my ($text) = @_; |
|---|
| 855 | |
|---|
| 856 | # try to see _if_ there is any img data for sorting |
|---|
| 857 | my $hasImageTag = ( $text =~ m/\<\s*img([^>]+)>/ ); |
|---|
| 858 | return $text if ( !$hasImageTag ); |
|---|
| 859 | |
|---|
| 860 | # first try to get the alt text |
|---|
| 861 | my $key = 'alt'; |
|---|
| 862 | $text =~ m/$key=\s*[\"\']([^\"\']*)/; |
|---|
| 863 | return $1 if ( $1 ne '' ); |
|---|
| 864 | |
|---|
| 865 | # else |
|---|
| 866 | |
|---|
| 867 | # no alt text; use the url |
|---|
| 868 | $key = 'url'; |
|---|
| 869 | $text =~ m/$key=\s*[\"\']([^\"\']*)/; |
|---|
| 870 | return $1 if ( $1 ne '' ); |
|---|
| 871 | |
|---|
| 872 | # else |
|---|
| 873 | |
|---|
| 874 | return $text; |
|---|
| 875 | } |
|---|
| 876 | |
|---|
| 877 | =pod |
|---|
| 878 | |
|---|
| 879 | Appends $className to $classList, separated by a space. |
|---|
| 880 | |
|---|
| 881 | =cut |
|---|
| 882 | |
|---|
| 883 | sub _appendToClassList { |
|---|
| 884 | my ( $classList, $className ) = @_; |
|---|
| 885 | $classList = $classList ? $classList .= ' ' : ''; |
|---|
| 886 | $classList .= $className; |
|---|
| 887 | return $classList; |
|---|
| 888 | } |
|---|
| 889 | |
|---|
| 890 | sub _appendSortedCssClass { |
|---|
| 891 | my ($classList) = @_; |
|---|
| 892 | |
|---|
| 893 | return _appendToClassList( $classList, 'foswikiSortedCol' ); |
|---|
| 894 | } |
|---|
| 895 | |
|---|
| 896 | sub _appendRowNumberCssClass { |
|---|
| 897 | my ( $classList, $colListName, $rowNum ) = @_; |
|---|
| 898 | |
|---|
| 899 | my $rowClassName = 'foswikiTableRow' . $colListName . $rowNum; |
|---|
| 900 | return _appendToClassList( $classList, $rowClassName ); |
|---|
| 901 | } |
|---|
| 902 | |
|---|
| 903 | sub _appendColNumberCssClass { |
|---|
| 904 | my ( $classList, $colNum ) = @_; |
|---|
| 905 | |
|---|
| 906 | my $colClassName = 'foswikiTableCol' . $colNum; |
|---|
| 907 | return _appendToClassList( $classList, $colClassName ); |
|---|
| 908 | } |
|---|
| 909 | |
|---|
| 910 | sub _appendFirstColumnCssClass { |
|---|
| 911 | my ($classList) = @_; |
|---|
| 912 | |
|---|
| 913 | return _appendToClassList( $classList, 'foswikiFirstCol' ); |
|---|
| 914 | } |
|---|
| 915 | |
|---|
| 916 | sub _appendLastColumnCssClass { |
|---|
| 917 | my ($classList) = @_; |
|---|
| 918 | |
|---|
| 919 | return _appendToClassList( $classList, 'foswikiLastCol' ); |
|---|
| 920 | } |
|---|
| 921 | |
|---|
| 922 | sub _appendLastRowCssClass { |
|---|
| 923 | my ($classList) = @_; |
|---|
| 924 | |
|---|
| 925 | return _appendToClassList( $classList, 'foswikiLast' ); |
|---|
| 926 | } |
|---|
| 927 | |
|---|
| 928 | sub _appendSortedAscendingCssClass { |
|---|
| 929 | my ($classList) = @_; |
|---|
| 930 | |
|---|
| 931 | return _appendToClassList( $classList, 'foswikiSortedAscendingCol' ); |
|---|
| 932 | } |
|---|
| 933 | |
|---|
| 934 | sub _appendSortedDescendingCssClass { |
|---|
| 935 | my ($classList) = @_; |
|---|
| 936 | |
|---|
| 937 | return _appendToClassList( $classList, 'foswikiSortedDescendingCol' ); |
|---|
| 938 | } |
|---|
| 939 | |
|---|
| 940 | # The default sort direction. |
|---|
| 941 | sub _getDefaultSortDirection { |
|---|
| 942 | return $SORT_DIRECTION->{'ASCENDING'}; |
|---|
| 943 | } |
|---|
| 944 | |
|---|
| 945 | # Gets the current sort direction. |
|---|
| 946 | sub _getCurrentSortDirection { |
|---|
| 947 | my ($currentDirection) = @_; |
|---|
| 948 | $currentDirection = $SORT_DIRECTION->{'ASCENDING'} |
|---|
| 949 | unless defined $currentDirection && $currentDirection =~ m/[0-2]+/; |
|---|
| 950 | $currentDirection ||= _getDefaultSortDirection(); |
|---|
| 951 | return $currentDirection; |
|---|
| 952 | } |
|---|
| 953 | |
|---|
| 954 | # Gets the new sort direction (needed for sort button) based on the current sort |
|---|
| 955 | # direction. |
|---|
| 956 | sub _getNewSortDirection { |
|---|
| 957 | my ($currentDirection) = @_; |
|---|
| 958 | if ( !defined $currentDirection ) { |
|---|
| 959 | return _getDefaultSortDirection(); |
|---|
| 960 | } |
|---|
| 961 | my $newDirection; |
|---|
| 962 | if ( $currentDirection == $SORT_DIRECTION->{'ASCENDING'} ) { |
|---|
| 963 | $newDirection = $SORT_DIRECTION->{'DESCENDING'}; |
|---|
| 964 | } |
|---|
| 965 | elsif ( $currentDirection == $SORT_DIRECTION->{'DESCENDING'} ) { |
|---|
| 966 | $newDirection = $SORT_DIRECTION->{'NONE'}; |
|---|
| 967 | } |
|---|
| 968 | elsif ( $currentDirection == $SORT_DIRECTION->{'NONE'} ) { |
|---|
| 969 | $newDirection = $SORT_DIRECTION->{'ASCENDING'}; |
|---|
| 970 | } |
|---|
| 971 | else { |
|---|
| 972 | $newDirection = _getDefaultSortDirection(); |
|---|
| 973 | } |
|---|
| 974 | |
|---|
| 975 | return $newDirection; |
|---|
| 976 | } |
|---|
| 977 | |
|---|
| 978 | =pod |
|---|
| 979 | |
|---|
| 980 | _createCssStyles( $writeDefaults, $inAttrs ) -> ($id, @styles) |
|---|
| 981 | |
|---|
| 982 | Explicitly set styles override html styling (in this file marked with comment '# html attribute'). |
|---|
| 983 | |
|---|
| 984 | =cut |
|---|
| 985 | |
|---|
| 986 | sub _createCssStyles { |
|---|
| 987 | my ( $writeDefaults, $inAttrs ) = @_; |
|---|
| 988 | |
|---|
| 989 | _debug("_createCssStyles; writeDefaults=$writeDefaults"); |
|---|
| 990 | |
|---|
| 991 | my $_styles = {}; |
|---|
| 992 | my $setAttribute = sub { |
|---|
| 993 | my ( $tableSelector, $type, $rule ) = @_; |
|---|
| 994 | |
|---|
| 995 | return if !$rule; |
|---|
| 996 | $type ||= '#'; # for table selector only, if no type |
|---|
| 997 | my $storedType = $_styles->{$tableSelector}->{$type} || ''; |
|---|
| 998 | if ( !defined $storedType ) { |
|---|
| 999 | @{ $_styles->{$tableSelector}->{$type} } = (); |
|---|
| 1000 | } |
|---|
| 1001 | if ( $rule ne $storedType ) { |
|---|
| 1002 | push @{ $_styles->{$tableSelector}->{$type} }, $rule; |
|---|
| 1003 | } |
|---|
| 1004 | }; |
|---|
| 1005 | |
|---|
| 1006 | if ( $writeDefaults && !$didWriteDefaultStyle ) { |
|---|
| 1007 | my $tableSelector = '.foswikiTable'; |
|---|
| 1008 | my $attr = 'padding-left:.3em; vertical-align:text-bottom'; |
|---|
| 1009 | &$setAttribute( $tableSelector, '.tableSortIcon img', $attr ); |
|---|
| 1010 | |
|---|
| 1011 | if ( $inAttrs->{cellpadding} ) { |
|---|
| 1012 | my $attr = |
|---|
| 1013 | 'padding:' . addDefaultSizeUnit( $inAttrs->{cellpadding} ); |
|---|
| 1014 | &$setAttribute( $tableSelector, 'td', $attr ); |
|---|
| 1015 | &$setAttribute( $tableSelector, 'th', $attr ); |
|---|
| 1016 | } |
|---|
| 1017 | } |
|---|
| 1018 | |
|---|
| 1019 | my $tableSelector; |
|---|
| 1020 | my $id; |
|---|
| 1021 | if ($writeDefaults) { |
|---|
| 1022 | $id = 'default'; |
|---|
| 1023 | $tableSelector = ".foswikiTable"; |
|---|
| 1024 | } |
|---|
| 1025 | else { |
|---|
| 1026 | $id = $inAttrs->{id}; |
|---|
| 1027 | $tableSelector = ".foswikiTable#$id"; |
|---|
| 1028 | } |
|---|
| 1029 | |
|---|
| 1030 | # tablerules |
|---|
| 1031 | if ( $inAttrs->{tableRules} ) { |
|---|
| 1032 | my @rules = @{ $inAttrs->{tableRules} }; |
|---|
| 1033 | |
|---|
| 1034 | my $attr_td; |
|---|
| 1035 | my $attr_th; |
|---|
| 1036 | foreach my $rule (@rules) { |
|---|
| 1037 | $attr_td = $TABLE_RULES->{$rule}->{TD} |
|---|
| 1038 | if $TABLE_RULES->{$rule}->{TD}; |
|---|
| 1039 | $attr_th = $TABLE_RULES->{$rule}->{TH} |
|---|
| 1040 | if $TABLE_RULES->{$rule}->{TH}; |
|---|
| 1041 | } |
|---|
| 1042 | &$setAttribute( $tableSelector, 'th', $attr_th ); |
|---|
| 1043 | &$setAttribute( $tableSelector, 'td', $attr_td ); |
|---|
| 1044 | } |
|---|
| 1045 | |
|---|
| 1046 | # tableframe |
|---|
| 1047 | if ( $inAttrs->{frame} ) { |
|---|
| 1048 | my $attr = $TABLE_FRAME->{ $inAttrs->{frame} }; |
|---|
| 1049 | &$setAttribute( $tableSelector, '', $attr ); |
|---|
| 1050 | } |
|---|
| 1051 | |
|---|
| 1052 | # tableborder |
|---|
| 1053 | if ( defined $inAttrs->{border} ) { |
|---|
| 1054 | my $tableBorderWidth = $inAttrs->{border} || 0; |
|---|
| 1055 | my $attr = 'border-width:' . addDefaultSizeUnit($tableBorderWidth); |
|---|
| 1056 | &$setAttribute( $tableSelector, '', $attr ); |
|---|
| 1057 | } |
|---|
| 1058 | |
|---|
| 1059 | # tableBorderColor |
|---|
| 1060 | if ( defined $inAttrs->{tableBorderColor} ) { |
|---|
| 1061 | my $attr; |
|---|
| 1062 | $attr = 'border-color:' . $inAttrs->{tableBorderColor}; |
|---|
| 1063 | &$setAttribute( $tableSelector, '', $attr ); |
|---|
| 1064 | $attr = 'border-top-color:' . $inAttrs->{tableBorderColor}; |
|---|
| 1065 | &$setAttribute( $tableSelector, '', $attr ); |
|---|
| 1066 | $attr = 'border-bottom-color:' . $inAttrs->{tableBorderColor}; |
|---|
| 1067 | &$setAttribute( $tableSelector, '', $attr ); |
|---|
| 1068 | $attr = 'border-left-color:' . $inAttrs->{tableBorderColor}; |
|---|
| 1069 | &$setAttribute( $tableSelector, '', $attr ); |
|---|
| 1070 | $attr = 'border-right-color:' . $inAttrs->{tableBorderColor}; |
|---|
| 1071 | &$setAttribute( $tableSelector, '', $attr ); |
|---|
| 1072 | } |
|---|
| 1073 | |
|---|
| 1074 | # cellSpacing |
|---|
| 1075 | if ( defined $inAttrs->{cellspacing} ) { |
|---|
| 1076 | |
|---|
| 1077 | # do not use border-collapse:collapse |
|---|
| 1078 | my $attr = 'border-collapse:separate'; |
|---|
| 1079 | &$setAttribute( $tableSelector, '', $attr ); |
|---|
| 1080 | } |
|---|
| 1081 | |
|---|
| 1082 | # cellpadding |
|---|
| 1083 | if ( defined $inAttrs->{cellpadding} ) { |
|---|
| 1084 | my $attr = 'padding:' . addDefaultSizeUnit( $inAttrs->{cellpadding} ); |
|---|
| 1085 | &$setAttribute( $tableSelector, 'td', $attr ); |
|---|
| 1086 | &$setAttribute( $tableSelector, 'th', $attr ); |
|---|
| 1087 | } |
|---|
| 1088 | |
|---|
| 1089 | # cellborder |
|---|
| 1090 | if ( defined $inAttrs->{cellBorder} ) { |
|---|
| 1091 | my $cellBorderWidth = $inAttrs->{cellBorder} || 0; |
|---|
| 1092 | my $attr = 'border-width:' . addDefaultSizeUnit($cellBorderWidth); |
|---|
| 1093 | &$setAttribute( $tableSelector, 'td', $attr ); |
|---|
| 1094 | &$setAttribute( $tableSelector, 'th', $attr ); |
|---|
| 1095 | } |
|---|
| 1096 | |
|---|
| 1097 | # tablewidth |
|---|
| 1098 | if ( defined $inAttrs->{width} ) { |
|---|
| 1099 | my $width = addDefaultSizeUnit( $inAttrs->{width} ); |
|---|
| 1100 | my $attr = 'width:' . $width; |
|---|
| 1101 | &$setAttribute( $tableSelector, '', $attr ); |
|---|
| 1102 | } |
|---|
| 1103 | |
|---|
| 1104 | # valign |
|---|
| 1105 | if ( defined $inAttrs->{vAlign} ) { |
|---|
| 1106 | my $attr = 'vertical-align:' . $inAttrs->{vAlign}; |
|---|
| 1107 | &$setAttribute( $tableSelector, 'td', $attr ); |
|---|
| 1108 | &$setAttribute( $tableSelector, 'th', $attr ); |
|---|
| 1109 | } |
|---|
| 1110 | |
|---|
| 1111 | # headerVAlign |
|---|
| 1112 | if ( defined $inAttrs->{headerVAlign} ) { |
|---|
| 1113 | my $attr = 'vertical-align:' . $inAttrs->{headerVAlign}; |
|---|
| 1114 | &$setAttribute( $tableSelector, 'th', $attr ); |
|---|
| 1115 | } |
|---|
| 1116 | |
|---|
| 1117 | # dataVAlign |
|---|
| 1118 | if ( defined $inAttrs->{dataVAlign} ) { |
|---|
| 1119 | my $attr = 'vertical-align:' . $inAttrs->{dataVAlign}; |
|---|
| 1120 | &$setAttribute( $tableSelector, 'td', $attr ); |
|---|
| 1121 | } |
|---|
| 1122 | |
|---|
| 1123 | # headerbg |
|---|
| 1124 | if ( defined $inAttrs->{headerBg} ) { |
|---|
| 1125 | my $color = |
|---|
| 1126 | ( $inAttrs->{headerBg} =~ /none/i ) |
|---|
| 1127 | ? 'transparent' |
|---|
| 1128 | : $inAttrs->{headerBg}; |
|---|
| 1129 | my $attr = 'background-color:' . $color; |
|---|
| 1130 | &$setAttribute( $tableSelector, 'th', $attr ); |
|---|
| 1131 | } |
|---|
| 1132 | |
|---|
| 1133 | # headerbgsorted |
|---|
| 1134 | if ( defined $inAttrs->{headerBgSorted} ) { |
|---|
| 1135 | my $color = |
|---|
| 1136 | ( $inAttrs->{headerBgSorted} =~ /none/i ) |
|---|
| 1137 | ? 'transparent' |
|---|
| 1138 | : $inAttrs->{headerBgSorted}; |
|---|
| 1139 | my $attr = 'background-color:' . $color; |
|---|
| 1140 | &$setAttribute( $tableSelector, 'th.foswikiSortedCol', $attr ); |
|---|
| 1141 | } |
|---|
| 1142 | |
|---|
| 1143 | # headercolor |
|---|
| 1144 | if ( defined $inAttrs->{headerColor} ) { |
|---|
| 1145 | my $attr = 'color:' . $inAttrs->{headerColor}; |
|---|
| 1146 | &$setAttribute( $tableSelector, 'th', $attr ); |
|---|
| 1147 | &$setAttribute( $tableSelector, 'th a:link', $attr ); |
|---|
| 1148 | &$setAttribute( $tableSelector, 'th a:visited', $attr ); |
|---|
| 1149 | &$setAttribute( $tableSelector, 'th a:xhover', $attr ) |
|---|
| 1150 | ; # just to make sorting work: hover should be last. below we will remove the x again. |
|---|
| 1151 | if ( defined $inAttrs->{headerBg} ) { |
|---|
| 1152 | my $hoverBackgroundColor = $inAttrs->{headerBg}; |
|---|
| 1153 | $attr = 'background-color:' . $hoverBackgroundColor; |
|---|
| 1154 | &$setAttribute( $tableSelector, 'th a:xhover', $attr ); |
|---|
| 1155 | } |
|---|
| 1156 | } |
|---|
| 1157 | |
|---|
| 1158 | # databg (array) |
|---|
| 1159 | if ( defined $inAttrs->{dataBgListRef} ) { |
|---|
| 1160 | my @dataBg = @{ $inAttrs->{dataBgListRef} }; |
|---|
| 1161 | my $noneColor = ( $dataBg[0] =~ /none/i ) ? 'transparent' : ''; |
|---|
| 1162 | my $count = 0; |
|---|
| 1163 | foreach my $color (@dataBg) { |
|---|
| 1164 | $color = $noneColor if $noneColor; |
|---|
| 1165 | next if !$color; |
|---|
| 1166 | my $rowSelector = 'foswikiTableRow' . 'dataBg' . $count; |
|---|
| 1167 | my $attr = "background-color:$color"; |
|---|
| 1168 | &$setAttribute( $tableSelector, "tr.$rowSelector td", $attr ); |
|---|
| 1169 | $count++; |
|---|
| 1170 | } |
|---|
| 1171 | } |
|---|
| 1172 | |
|---|
| 1173 | # databgsorted (array) |
|---|
| 1174 | if ( defined $inAttrs->{dataBgSortedListRef} ) { |
|---|
| 1175 | my @dataBgSorted = @{ $inAttrs->{dataBgSortedListRef} }; |
|---|
| 1176 | my $noneColor = ( $dataBgSorted[0] =~ /none/i ) ? 'transparent' : ''; |
|---|
| 1177 | my $count = 0; |
|---|
| 1178 | foreach my $color (@dataBgSorted) { |
|---|
| 1179 | $color = $noneColor if $noneColor; |
|---|
| 1180 | next if !$color; |
|---|
| 1181 | my $rowSelector = 'foswikiTableRow' . 'dataBg' . $count; |
|---|
| 1182 | my $attr = "background-color:$color"; |
|---|
| 1183 | &$setAttribute( $tableSelector, |
|---|
| 1184 | "tr.$rowSelector td.foswikiSortedCol", $attr ); |
|---|
| 1185 | $count++; |
|---|
| 1186 | } |
|---|
| 1187 | } |
|---|
| 1188 | |
|---|
| 1189 | # datacolor (array) |
|---|
| 1190 | if ( defined $inAttrs->{dataColorListRef} ) { |
|---|
| 1191 | my @dataColor = @{ $inAttrs->{dataColorListRef} }; |
|---|
| 1192 | unless ( $dataColor[0] =~ /none/i ) { |
|---|
| 1193 | my $count = 0; |
|---|
| 1194 | foreach my $color (@dataColor) { |
|---|
| 1195 | next if !$color; |
|---|
| 1196 | my $rowSelector = 'foswikiTableRow' . 'dataColor' . $count; |
|---|
| 1197 | my $attr = "color:$color"; |
|---|
| 1198 | &$setAttribute( $tableSelector, "tr.$rowSelector td", $attr ); |
|---|
| 1199 | $count++; |
|---|
| 1200 | } |
|---|
| 1201 | } |
|---|
| 1202 | } |
|---|
| 1203 | |
|---|
| 1204 | # columnwidths |
|---|
| 1205 | if ( defined $inAttrs->{columnWidthsListRef} ) { |
|---|
| 1206 | my @columnWidths = @{ $inAttrs->{columnWidthsListRef} }; |
|---|
| 1207 | my $count = 0; |
|---|
| 1208 | foreach my $width (@columnWidths) { |
|---|
| 1209 | next if !$width; |
|---|
| 1210 | $width = addDefaultSizeUnit($width); |
|---|
| 1211 | my $colSelector = 'foswikiTableCol'; |
|---|
| 1212 | $colSelector .= $count; |
|---|
| 1213 | my $attr = 'width:' . $width; |
|---|
| 1214 | &$setAttribute( $tableSelector, "td.$colSelector", $attr ); |
|---|
| 1215 | &$setAttribute( $tableSelector, "th.$colSelector", $attr ); |
|---|
| 1216 | $count++; |
|---|
| 1217 | } |
|---|
| 1218 | } |
|---|
| 1219 | |
|---|
| 1220 | # headeralign |
|---|
| 1221 | if ( defined $inAttrs->{headerAlignListRef} ) { |
|---|
| 1222 | my @headerAlign = @{ $inAttrs->{headerAlignListRef} }; |
|---|
| 1223 | if ( scalar @headerAlign == 1 ) { |
|---|
| 1224 | my $align = $headerAlign[0]; |
|---|
| 1225 | my $attr = 'text-align:' . $align; |
|---|
| 1226 | &$setAttribute( $tableSelector, 'th', $attr ); |
|---|
| 1227 | } |
|---|
| 1228 | else { |
|---|
| 1229 | my $count = 0; |
|---|
| 1230 | foreach my $align (@headerAlign) { |
|---|
| 1231 | next if !$align; |
|---|
| 1232 | my $colSelector = 'foswikiTableCol'; |
|---|
| 1233 | $colSelector .= $count; |
|---|
| 1234 | my $attr = 'text-align:' . $align; |
|---|
| 1235 | &$setAttribute( $tableSelector, "th.$colSelector", $attr ); |
|---|
| 1236 | $count++; |
|---|
| 1237 | } |
|---|
| 1238 | } |
|---|
| 1239 | } |
|---|
| 1240 | |
|---|
| 1241 | # dataAlign |
|---|
| 1242 | if ( defined $inAttrs->{dataAlignListRef} ) { |
|---|
| 1243 | my @dataAlign = @{ $inAttrs->{dataAlignListRef} }; |
|---|
| 1244 | if ( scalar @dataAlign == 1 ) { |
|---|
| 1245 | my $align = $dataAlign[0]; |
|---|
| 1246 | my $attr = 'text-align:' . $align; |
|---|
| 1247 | &$setAttribute( $tableSelector, 'td', $attr ); |
|---|
| 1248 | } |
|---|
| 1249 | else { |
|---|
| 1250 | my $count = 0; |
|---|
| 1251 | foreach my $align (@dataAlign) { |
|---|
| 1252 | next if !$align; |
|---|
| 1253 | my $colSelector = 'foswikiTableCol'; |
|---|
| 1254 | $colSelector .= $count; |
|---|
| 1255 | my $attr = 'text-align:' . $align; |
|---|
| 1256 | &$setAttribute( $tableSelector, "td.$colSelector", $attr ); |
|---|
| 1257 | $count++; |
|---|
| 1258 | } |
|---|
| 1259 | } |
|---|
| 1260 | } |
|---|
| 1261 | |
|---|
| 1262 | my @styles = (); |
|---|
| 1263 | foreach my $tableSelector ( sort keys %{$_styles} ) { |
|---|
| 1264 | foreach my $selector ( sort keys %{ $_styles->{$tableSelector} } ) { |
|---|
| 1265 | my $selectors = |
|---|
| 1266 | join( '; ', @{ $_styles->{$tableSelector}->{$selector} } ); |
|---|
| 1267 | $selector =~ s/xhover/hover/go; # remove sorting hack |
|---|
| 1268 | # TODO: optimize by combining identical rules |
|---|
| 1269 | if ( $selector eq '#' ) { |
|---|
| 1270 | push @styles, "$tableSelector {$selectors}"; |
|---|
| 1271 | } |
|---|
| 1272 | else { |
|---|
| 1273 | push @styles, "$tableSelector $selector {$selectors}"; |
|---|
| 1274 | } |
|---|
| 1275 | } |
|---|
| 1276 | } |
|---|
| 1277 | |
|---|
| 1278 | return ( $id, @styles ); |
|---|
| 1279 | } |
|---|
| 1280 | |
|---|
| 1281 | sub _addHeadStyles { |
|---|
| 1282 | my ( $inId, @inStyles ) = @_; |
|---|
| 1283 | |
|---|
| 1284 | return if !scalar @inStyles; |
|---|
| 1285 | |
|---|
| 1286 | $styles->{seendIds}->{$inId} = 1; |
|---|
| 1287 | if ( $inId eq $HEAD_ID_DEFAULT_STYLE ) { |
|---|
| 1288 | $styles->{$HEAD_ID_DEFAULT_STYLE}->{'default'} = \@inStyles; |
|---|
| 1289 | _writeStyleToHead( $HEAD_ID_DEFAULT_STYLE, |
|---|
| 1290 | $styles->{$HEAD_ID_DEFAULT_STYLE} ); |
|---|
| 1291 | } |
|---|
| 1292 | else { |
|---|
| 1293 | $styles->{$HEAD_ID_SPECIFIC_STYLE}->{$inId} = \@inStyles; |
|---|
| 1294 | _writeStyleToHead( $HEAD_ID_SPECIFIC_STYLE, |
|---|
| 1295 | $styles->{$HEAD_ID_SPECIFIC_STYLE} ); |
|---|
| 1296 | } |
|---|
| 1297 | } |
|---|
| 1298 | |
|---|
| 1299 | sub _writeStyleToHead { |
|---|
| 1300 | my ( $inId, $inStyles ) = @_; |
|---|
| 1301 | |
|---|
| 1302 | my @allStyles = (); |
|---|
| 1303 | foreach my $id ( sort keys %{$inStyles} ) { |
|---|
| 1304 | push @allStyles, @{ $inStyles->{$id} }; |
|---|
| 1305 | } |
|---|
| 1306 | my $styleText = join( "\n", @allStyles ); |
|---|
| 1307 | |
|---|
| 1308 | my $header = <<EOS; |
|---|
| 1309 | <style type="text/css" media="all"> |
|---|
| 1310 | $styleText |
|---|
| 1311 | </style> |
|---|
| 1312 | EOS |
|---|
| 1313 | $header =~ s/(.*?)\s*$/$1/; # remove last newline |
|---|
| 1314 | Foswiki::Func::addToHEAD( $inId, $header, $HEAD_ID_DEFAULT_STYLE ); |
|---|
| 1315 | } |
|---|
| 1316 | |
|---|
| 1317 | =pod |
|---|
| 1318 | |
|---|
| 1319 | StaticMethod addDefaultSizeUnit ($text) -> $text |
|---|
| 1320 | |
|---|
| 1321 | Adds size unit 'px' if this is missing from the size text. |
|---|
| 1322 | |
|---|
| 1323 | =cut |
|---|
| 1324 | |
|---|
| 1325 | sub addDefaultSizeUnit { |
|---|
| 1326 | my ($inSize) = @_; |
|---|
| 1327 | |
|---|
| 1328 | my $unit = ''; |
|---|
| 1329 | if ( $inSize =~ m/$PATTERN_ATTRIBUTE_SIZE/ ) { |
|---|
| 1330 | $unit = 'px' if !$2; |
|---|
| 1331 | } |
|---|
| 1332 | return "$inSize$unit"; |
|---|
| 1333 | } |
|---|
| 1334 | |
|---|
| 1335 | sub emitTable { |
|---|
| 1336 | |
|---|
| 1337 | _addDefaultStyles(); |
|---|
| 1338 | |
|---|
| 1339 | _debug('emitTable'); |
|---|
| 1340 | |
|---|
| 1341 | #Validate headerrows/footerrows and modify if out of range |
|---|
| 1342 | if ( $combinedTableAttrs->{headerrows} > scalar @curTable ) { |
|---|
| 1343 | $combinedTableAttrs->{headerrows} = |
|---|
| 1344 | scalar @curTable; # limit header to size of table! |
|---|
| 1345 | } |
|---|
| 1346 | if ( $combinedTableAttrs->{headerrows} + $combinedTableAttrs->{footerrows} > |
|---|
| 1347 | @curTable ) |
|---|
| 1348 | { |
|---|
| 1349 | $combinedTableAttrs->{footerrows} = scalar @curTable - |
|---|
| 1350 | $combinedTableAttrs->{headerrows}; # and footer to whatever is left |
|---|
| 1351 | } |
|---|
| 1352 | |
|---|
| 1353 | my $sortThisTable = |
|---|
| 1354 | ( !defined $combinedTableAttrs->{sortAllTables} |
|---|
| 1355 | || $combinedTableAttrs->{sortAllTables} == 0 ) |
|---|
| 1356 | ? 0 |
|---|
| 1357 | : $combinedTableAttrs->{sort}; |
|---|
| 1358 | |
|---|
| 1359 | if ( $combinedTableAttrs->{headerrows} == 0 ) { |
|---|
| 1360 | my ( $headerRowCount, $footerRowCount ) = _headerRowCount( \@curTable ); |
|---|
| 1361 | |
|---|
| 1362 | # override default setting with calculated header count |
|---|
| 1363 | $combinedTableAttrs->{headerrows} = $headerRowCount; |
|---|
| 1364 | $combinedTableAttrs->{footerrows} = $footerRowCount; |
|---|
| 1365 | } |
|---|
| 1366 | |
|---|
| 1367 | my $tableTagAttributes = {}; |
|---|
| 1368 | $tableTagAttributes->{class} = $combinedTableAttrs->{class}; |
|---|
| 1369 | $tableTagAttributes->{border} = $combinedTableAttrs->{border}; |
|---|
| 1370 | $tableTagAttributes->{cellspacing} = $combinedTableAttrs->{cellspacing}; |
|---|
| 1371 | $tableTagAttributes->{cellpadding} = $combinedTableAttrs->{cellpadding}; |
|---|
| 1372 | $tableTagAttributes->{id} = $combinedTableAttrs->{id} |
|---|
| 1373 | || undef; |
|---|
| 1374 | $tableTagAttributes->{summary} = $combinedTableAttrs->{summary}; |
|---|
| 1375 | $tableTagAttributes->{frame} = $combinedTableAttrs->{frame}; |
|---|
| 1376 | $tableTagAttributes->{rules} = $combinedTableAttrs->{rules}; |
|---|
| 1377 | $tableTagAttributes->{width} = $combinedTableAttrs->{width}; |
|---|
| 1378 | |
|---|
| 1379 | # remove empty attributes |
|---|
| 1380 | while ( my ( $key, $value ) = each %{$tableTagAttributes} ) { |
|---|
| 1381 | delete $tableTagAttributes->{$key} if !defined $value || $value eq ''; |
|---|
| 1382 | } |
|---|
| 1383 | |
|---|
| 1384 | my $text = $currTablePre . CGI::start_table($tableTagAttributes); |
|---|
| 1385 | $text .= $currTablePre . CGI::caption( $combinedTableAttrs->{tableCaption} ) |
|---|
| 1386 | if $combinedTableAttrs->{tableCaption}; |
|---|
| 1387 | |
|---|
| 1388 | # count the number of cols to prevent looping over non-existing columns |
|---|
| 1389 | my $maxCols = 0; |
|---|
| 1390 | |
|---|
| 1391 | # Flush out any remaining rowspans |
|---|
| 1392 | for ( my $i = 0 ; $i < @rowspan ; $i++ ) { |
|---|
| 1393 | if ( defined( $rowspan[$i] ) && $rowspan[$i] ) { |
|---|
| 1394 | my $nRows = scalar(@curTable); |
|---|
| 1395 | my $rspan = $rowspan[$i] + 1; |
|---|
| 1396 | my $r = $nRows - $rspan; |
|---|
| 1397 | $curTable[$r][$i]->{attrs} ||= {}; |
|---|
| 1398 | if ( $rspan > 1 ) { |
|---|
| 1399 | $curTable[$r][$i]->{attrs}->{rowspan} = $rspan; |
|---|
| 1400 | } |
|---|
| 1401 | } |
|---|
| 1402 | } |
|---|
| 1403 | |
|---|
| 1404 | if ( |
|---|
| 1405 | ( |
|---|
| 1406 | $sortThisTable |
|---|
| 1407 | && defined $sortCol |
|---|
| 1408 | && defined $requestedTable |
|---|
| 1409 | && $requestedTable == $tableCount |
|---|
| 1410 | ) |
|---|
| 1411 | || defined $combinedTableAttrs->{initSort} |
|---|
| 1412 | ) |
|---|
| 1413 | { |
|---|
| 1414 | |
|---|
| 1415 | # DG 08 Aug 2002: Allow multi-line headers |
|---|
| 1416 | my @header = splice( @curTable, 0, $combinedTableAttrs->{headerrows} ); |
|---|
| 1417 | |
|---|
| 1418 | # DG 08 Aug 2002: Skip sorting any trailers as well |
|---|
| 1419 | my @trailer = (); |
|---|
| 1420 | if ( $combinedTableAttrs->{footerrows} |
|---|
| 1421 | && scalar(@curTable) > $combinedTableAttrs->{footerrows} ) |
|---|
| 1422 | { |
|---|
| 1423 | @trailer = splice( @curTable, -$combinedTableAttrs->{footerrows} ); |
|---|
| 1424 | } |
|---|
| 1425 | |
|---|
| 1426 | # Count the maximum number of columns of this table |
|---|
| 1427 | for my $row ( 0 .. $#curTable ) { |
|---|
| 1428 | my $thisRowMaxColCount = 0; |
|---|
| 1429 | for my $col ( 0 .. $#{ $curTable[$row] } ) { |
|---|
| 1430 | $thisRowMaxColCount++; |
|---|
| 1431 | } |
|---|
| 1432 | $maxCols = $thisRowMaxColCount |
|---|
| 1433 | if ( $thisRowMaxColCount > $maxCols ); |
|---|
| 1434 | } |
|---|
| 1435 | |
|---|
| 1436 | # Handle multi-row labels by killing rowspans in sorted tables |
|---|
| 1437 | for my $row ( 0 .. $#curTable ) { |
|---|
| 1438 | for my $col ( 0 .. $#{ $curTable[$row] } ) { |
|---|
| 1439 | |
|---|
| 1440 | # SMELL: why do we need to specify a rowspan of 1? |
|---|
| 1441 | $curTable[$row][$col]->{attrs}->{rowspan} = 1; |
|---|
| 1442 | if ( $curTable[$row][$col]->{type} eq 'Y' ) { |
|---|
| 1443 | $curTable[$row][$col]->{text} = |
|---|
| 1444 | $curTable[ $row - 1 ][$col]->{text}; |
|---|
| 1445 | $curTable[$row][$col]->{type} = 'td'; |
|---|
| 1446 | } |
|---|
| 1447 | } |
|---|
| 1448 | } |
|---|
| 1449 | |
|---|
| 1450 | # url requested sort on column beyond end of table. Force to last column |
|---|
| 1451 | $sortCol = 0 unless ( $sortCol =~ m/^[0-9]+$/ ); |
|---|
| 1452 | $sortCol = $maxCols - 1 if ( $sortCol >= $maxCols ); |
|---|
| 1453 | |
|---|
| 1454 | # only get the column type if within bounds |
|---|
| 1455 | if ( $sortCol < $maxCols ) { |
|---|
| 1456 | _setSortTypeForCells( $sortCol, \@curTable ); |
|---|
| 1457 | } |
|---|
| 1458 | |
|---|
| 1459 | _debug("currentSortDirection:$currentSortDirection"); |
|---|
| 1460 | |
|---|
| 1461 | if ( $combinedTableAttrs->{sort} |
|---|
| 1462 | && $currentSortDirection == $SORT_DIRECTION->{'ASCENDING'} ) |
|---|
| 1463 | { |
|---|
| 1464 | @curTable = sort { |
|---|
| 1465 | $a->[$sortCol]->{sortText} cmp $b->[$sortCol]->{sortText} |
|---|
| 1466 | || $a->[$sortCol]->{number} <=> $b->[$sortCol]->{number} |
|---|
| 1467 | || $a->[$sortCol]->{dateString} |
|---|
| 1468 | cmp $b->[$sortCol]->{dateString} |
|---|
| 1469 | } @curTable; |
|---|
| 1470 | } |
|---|
| 1471 | elsif ($combinedTableAttrs->{sort} |
|---|
| 1472 | && $currentSortDirection == $SORT_DIRECTION->{'DESCENDING'} ) |
|---|
| 1473 | { |
|---|
| 1474 | @curTable = sort { |
|---|
| 1475 | $b->[$sortCol]->{sortText} cmp $a->[$sortCol]->{sortText} |
|---|
| 1476 | || $b->[$sortCol]->{number} <=> $a->[$sortCol]->{number} |
|---|
| 1477 | || $b->[$sortCol]->{dateString} |
|---|
| 1478 | cmp $a->[$sortCol]->{dateString} |
|---|
| 1479 | } @curTable; |
|---|
| 1480 | } |
|---|
| 1481 | |
|---|
| 1482 | # DG 08 Aug 2002: Cleanup after the header/trailer splicing |
|---|
| 1483 | # this is probably awfully inefficient - but how big is a table? |
|---|
| 1484 | @curTable = ( @header, @curTable, @trailer ); |
|---|
| 1485 | } # if defined $sortCol ... |
|---|
| 1486 | |
|---|
| 1487 | my $rowCount = 0; |
|---|
| 1488 | my $numberOfRows = scalar(@curTable); |
|---|
| 1489 | my $dataColorCount = 0; |
|---|
| 1490 | |
|---|
| 1491 | my @headerRowList = (); |
|---|
| 1492 | my @bodyRowList = (); |
|---|
| 1493 | my @footerRowList = (); |
|---|
| 1494 | |
|---|
| 1495 | my $isPastHeaderRows = 0; |
|---|
| 1496 | my $singleIndent = "\n\t"; |
|---|
| 1497 | my $doubleIndent = "\n\t\t"; |
|---|
| 1498 | my $tripleIndent = "\n\t\t\t"; |
|---|
| 1499 | |
|---|
| 1500 | # Only *one* row of the table has sort links, and it will either |
|---|
| 1501 | # be the last row in the header or the first row in the footer. |
|---|
| 1502 | my $sortLinksWritten = 0; |
|---|
| 1503 | |
|---|
| 1504 | foreach my $row (@curTable) { |
|---|
| 1505 | my $rowtext = ''; |
|---|
| 1506 | my $colCount = 0; |
|---|
| 1507 | |
|---|
| 1508 | # keep track of header cells: if all cells are header cells, do not |
|---|
| 1509 | # update the data color count |
|---|
| 1510 | my $headerCellCount = 0; |
|---|
| 1511 | my $numberOfCols = scalar(@$row); |
|---|
| 1512 | my $writingSortLinks = 0; |
|---|
| 1513 | |
|---|
| 1514 | foreach my $fcell (@$row) { |
|---|
| 1515 | |
|---|
| 1516 | # check if cell exists |
|---|
| 1517 | next if ( !$fcell || !$fcell->{type} ); |
|---|
| 1518 | |
|---|
| 1519 | my $tableAnchor = ''; |
|---|
| 1520 | next |
|---|
| 1521 | if ( $fcell->{type} eq 'X' ) |
|---|
| 1522 | ; # data was there so sort could work with col spanning |
|---|
| 1523 | my $type = $fcell->{type}; |
|---|
| 1524 | my $cell = $fcell->{text}; |
|---|
| 1525 | my $attr = $fcell->{attrs} || {}; |
|---|
| 1526 | |
|---|
| 1527 | my $newDirection; |
|---|
| 1528 | my $isSorted = 0; |
|---|
| 1529 | |
|---|
| 1530 | if ( |
|---|
| 1531 | $currentSortDirection != $SORT_DIRECTION->{'NONE'} |
|---|
| 1532 | && defined $sortCol |
|---|
| 1533 | && $colCount == $sortCol |
|---|
| 1534 | |
|---|
| 1535 | # Removing the line below hides the marking of sorted columns |
|---|
| 1536 | # until the user clicks on a header (KJL) |
|---|
| 1537 | # && defined $requestedTable && $requestedTable == $tableCount |
|---|
| 1538 | # && $sortType ne '' |
|---|
| 1539 | ) |
|---|
| 1540 | { |
|---|
| 1541 | $isSorted = 1; |
|---|
| 1542 | $newDirection = _getNewSortDirection($currentSortDirection); |
|---|
| 1543 | } |
|---|
| 1544 | else { |
|---|
| 1545 | $newDirection = _getDefaultSortDirection(); |
|---|
| 1546 | } |
|---|
| 1547 | |
|---|
| 1548 | if ( $type eq 'th' ) { |
|---|
| 1549 | $headerCellCount++; |
|---|
| 1550 | |
|---|
| 1551 | # html attribute: bgcolor |
|---|
| 1552 | if ( $combinedTableAttrs->{generateInlineMarkup} |
|---|
| 1553 | && defined $combinedTableAttrs->{headerBg} ) |
|---|
| 1554 | { |
|---|
| 1555 | $attr->{bgcolor} = $combinedTableAttrs->{headerBg} |
|---|
| 1556 | unless ( $combinedTableAttrs->{headerBg} =~ /none/i ); |
|---|
| 1557 | } |
|---|
| 1558 | |
|---|
| 1559 | # END html attribute |
|---|
| 1560 | |
|---|
| 1561 | if ($isSorted) { |
|---|
| 1562 | if ( $currentSortDirection == |
|---|
| 1563 | $SORT_DIRECTION->{'ASCENDING'} ) |
|---|
| 1564 | { |
|---|
| 1565 | $tableAnchor = $CHAR_SORT_ASCENDING; |
|---|
| 1566 | } |
|---|
| 1567 | if ( $currentSortDirection == |
|---|
| 1568 | $SORT_DIRECTION->{'DESCENDING'} ) |
|---|
| 1569 | { |
|---|
| 1570 | $tableAnchor = $CHAR_SORT_DESCENDING; |
|---|
| 1571 | } |
|---|
| 1572 | |
|---|
| 1573 | # html attribute: (sorted header cell) bgcolor |
|---|
| 1574 | # overrides earlier set bgcolor |
|---|
| 1575 | if ( $combinedTableAttrs->{generateInlineMarkup} |
|---|
| 1576 | && defined $combinedTableAttrs->{headerBgSorted} ) |
|---|
| 1577 | { |
|---|
| 1578 | $attr->{bgcolor} = $combinedTableAttrs->{headerBgSorted} |
|---|
| 1579 | unless ( |
|---|
| 1580 | $combinedTableAttrs->{headerBgSorted} =~ /none/i ); |
|---|
| 1581 | } |
|---|
| 1582 | |
|---|
| 1583 | # END html attribute |
|---|
| 1584 | } |
|---|
| 1585 | |
|---|
| 1586 | if ( |
|---|
| 1587 | defined $sortCol |
|---|
| 1588 | && $colCount == $sortCol |
|---|
| 1589 | && defined $requestedTable |
|---|
| 1590 | && $requestedTable == $tableCount |
|---|
| 1591 | && ( $combinedTableAttrs->{headerrows} |
|---|
| 1592 | || $combinedTableAttrs->{footerrows} ) |
|---|
| 1593 | ) |
|---|
| 1594 | { |
|---|
| 1595 | |
|---|
| 1596 | $tableAnchor = |
|---|
| 1597 | CGI::a( { name => 'sorted_table' }, '<!-- -->' ) |
|---|
| 1598 | . $tableAnchor; |
|---|
| 1599 | } |
|---|
| 1600 | |
|---|
| 1601 | # html attribute: headercolor (font style) |
|---|
| 1602 | if ( $combinedTableAttrs->{generateInlineMarkup} |
|---|
| 1603 | && defined $combinedTableAttrs->{headerColor} ) |
|---|
| 1604 | { |
|---|
| 1605 | my $fontStyle = |
|---|
| 1606 | { color => $combinedTableAttrs->{headerColor} }; |
|---|
| 1607 | $cell = CGI::font( $fontStyle, $cell ); |
|---|
| 1608 | } |
|---|
| 1609 | |
|---|
| 1610 | # END html attribute |
|---|
| 1611 | |
|---|
| 1612 | if ( |
|---|
| 1613 | $sortThisTable |
|---|
| 1614 | && ( |
|---|
| 1615 | ( $rowCount == $combinedTableAttrs->{headerrows} - 1 ) |
|---|
| 1616 | || ( !$combinedTableAttrs->{headerrows} |
|---|
| 1617 | && $rowCount == |
|---|
| 1618 | $numberOfRows - $combinedTableAttrs->{footerrows} ) |
|---|
| 1619 | ) |
|---|
| 1620 | && ( $writingSortLinks || !$sortLinksWritten ) |
|---|
| 1621 | ) |
|---|
| 1622 | { |
|---|
| 1623 | $writingSortLinks = 1; |
|---|
| 1624 | my $linkAttributes = { |
|---|
| 1625 | href => $url |
|---|
| 1626 | . 'sortcol=' |
|---|
| 1627 | . $colCount |
|---|
| 1628 | . ';table=' |
|---|
| 1629 | . $tableCount . ';up=' |
|---|
| 1630 | . $newDirection |
|---|
| 1631 | . '#sorted_table', |
|---|
| 1632 | rel => 'nofollow', |
|---|
| 1633 | title => 'Sort by this column' |
|---|
| 1634 | }; |
|---|
| 1635 | |
|---|
| 1636 | if ( $cell =~ /\[\[|href/o ) { |
|---|
| 1637 | $cell .= CGI::a( $linkAttributes, $CHAR_SORT_BOTH ) |
|---|
| 1638 | . $tableAnchor; |
|---|
| 1639 | } |
|---|
| 1640 | else { |
|---|
| 1641 | $cell = CGI::a( $linkAttributes, $cell ) . $tableAnchor; |
|---|
| 1642 | } |
|---|
| 1643 | } |
|---|
| 1644 | |
|---|
| 1645 | } |
|---|
| 1646 | else { |
|---|
| 1647 | |
|---|
| 1648 | $type = 'td' unless $type eq 'Y'; |
|---|
| 1649 | |
|---|
| 1650 | # html attribute: bgcolor |
|---|
| 1651 | if ( $combinedTableAttrs->{generateInlineMarkup} ) { |
|---|
| 1652 | if ( $isSorted |
|---|
| 1653 | && defined $combinedTableAttrs->{dataBgSortedListRef} ) |
|---|
| 1654 | { |
|---|
| 1655 | my @dataBg = |
|---|
| 1656 | @{ $combinedTableAttrs->{dataBgSortedListRef} }; |
|---|
| 1657 | |
|---|
| 1658 | unless ( $dataBg[0] =~ /none/ ) { |
|---|
| 1659 | $attr->{bgcolor} = |
|---|
| 1660 | $dataBg[ $dataColorCount % ( $#dataBg + 1 ) ]; |
|---|
| 1661 | } |
|---|
| 1662 | } |
|---|
| 1663 | elsif ( defined $combinedTableAttrs->{dataBgListRef} ) { |
|---|
| 1664 | my @dataBg = @{ $combinedTableAttrs->{dataBgListRef} }; |
|---|
| 1665 | unless ( $dataBg[0] =~ /none/i ) { |
|---|
| 1666 | $attr->{bgcolor} = |
|---|
| 1667 | $dataBg[ $dataColorCount % ( $#dataBg + 1 ) ]; |
|---|
| 1668 | } |
|---|
| 1669 | } |
|---|
| 1670 | } |
|---|
| 1671 | |
|---|
| 1672 | # END html attribute |
|---|
| 1673 | |
|---|
| 1674 | # html attribute: datacolor (font style) |
|---|
| 1675 | if ( $combinedTableAttrs->{generateInlineMarkup} |
|---|
| 1676 | && defined $combinedTableAttrs->{dataColorListRef} ) |
|---|
| 1677 | { |
|---|
| 1678 | my @dataColor = |
|---|
| 1679 | @{ $combinedTableAttrs->{dataColorListRef} }; |
|---|
| 1680 | my $color = |
|---|
| 1681 | $dataColor[ $dataColorCount % ( $#dataColor + 1 ) ]; |
|---|
| 1682 | unless ( $color =~ /^(none)$/i ) { |
|---|
| 1683 | my $cellAttrs = { color => $color }; |
|---|
| 1684 | $cell = CGI::font( $cellAttrs, ' ' . $cell . ' ' ); |
|---|
| 1685 | } |
|---|
| 1686 | } |
|---|
| 1687 | |
|---|
| 1688 | # END html attribute |
|---|
| 1689 | |
|---|
| 1690 | } ###if( $type eq 'th' ) |
|---|
| 1691 | |
|---|
| 1692 | if ($isSorted) { |
|---|
| 1693 | $attr->{class} = _appendSortedCssClass( $attr->{class} ); |
|---|
| 1694 | } |
|---|
| 1695 | |
|---|
| 1696 | if ($writingSortLinks) { |
|---|
| 1697 | $sortLinksWritten = 1; |
|---|
| 1698 | } |
|---|
| 1699 | |
|---|
| 1700 | my $isLastRow = ( $rowCount == $numberOfRows - 1 ); |
|---|
| 1701 | if ( $attr->{rowspan} ) { |
|---|
| 1702 | $isLastRow = |
|---|
| 1703 | ( ( $rowCount + ( $attr->{rowspan} - 1 ) ) == |
|---|
| 1704 | $numberOfRows - 1 ); |
|---|
| 1705 | } |
|---|
| 1706 | |
|---|
| 1707 | # CSS class name |
|---|
| 1708 | $attr->{class} = _appendFirstColumnCssClass( $attr->{class} ) |
|---|
| 1709 | if $colCount == 0; |
|---|
| 1710 | my $isLastCol = ( $colCount == $numberOfCols - 1 ); |
|---|
| 1711 | $attr->{class} = _appendLastColumnCssClass( $attr->{class} ) |
|---|
| 1712 | if $isLastCol; |
|---|
| 1713 | |
|---|
| 1714 | $attr->{class} = _appendLastRowCssClass( $attr->{class} ) |
|---|
| 1715 | if $isLastRow; |
|---|
| 1716 | |
|---|
| 1717 | $colCount++; |
|---|
| 1718 | next if ( $type eq 'Y' ); |
|---|
| 1719 | my $fn = 'CGI::' . $type; |
|---|
| 1720 | no strict 'refs'; |
|---|
| 1721 | $rowtext .= "$tripleIndent" . &$fn( $attr, " $cell " ); |
|---|
| 1722 | use strict 'refs'; |
|---|
| 1723 | } # foreach my $fcell ( @$row ) |
|---|
| 1724 | |
|---|
| 1725 | # assign css class names to tr |
|---|
| 1726 | # based on settings: dataBg, dataBgSorted |
|---|
| 1727 | my $trClassName = ''; |
|---|
| 1728 | |
|---|
| 1729 | # just 2 css names is too limited, but we will keep it for compatibility |
|---|
| 1730 | # with existing style sheets |
|---|
| 1731 | my $rowTypeName = |
|---|
| 1732 | ( $rowCount % 2 ) ? 'foswikiTableEven' : 'foswikiTableOdd'; |
|---|
| 1733 | $trClassName = _appendToClassList( $trClassName, $rowTypeName ); |
|---|
| 1734 | |
|---|
| 1735 | if ( $combinedTableAttrs->{dataBgSortedListRef} ) { |
|---|
| 1736 | my @dataBgSorted = @{ $combinedTableAttrs->{dataBgSortedListRef} }; |
|---|
| 1737 | my $modRowNum = $dataColorCount % ( $#dataBgSorted + 1 ); |
|---|
| 1738 | $trClassName = |
|---|
| 1739 | _appendRowNumberCssClass( $trClassName, 'dataBgSorted', |
|---|
| 1740 | $modRowNum ); |
|---|
| 1741 | } |
|---|
| 1742 | if ( $combinedTableAttrs->{dataBgListRef} ) { |
|---|
| 1743 | my @dataBg = @{ $combinedTableAttrs->{dataBgListRef} }; |
|---|
| 1744 | my $modRowNum = $dataColorCount % ( $#dataBg + 1 ); |
|---|
| 1745 | $trClassName = |
|---|
| 1746 | _appendRowNumberCssClass( $trClassName, 'dataBg', $modRowNum ); |
|---|
| 1747 | } |
|---|
| 1748 | if ( $combinedTableAttrs->{dataColorListRef} ) { |
|---|
| 1749 | my @dataColor = @{ $combinedTableAttrs->{dataColorListRef} }; |
|---|
| 1750 | my $modRowNum = $dataColorCount % ( $#dataColor + 1 ); |
|---|
| 1751 | $trClassName = |
|---|
| 1752 | _appendRowNumberCssClass( $trClassName, 'dataColor', $modRowNum ); |
|---|
| 1753 | } |
|---|
| 1754 | $rowtext .= $doubleIndent; |
|---|
| 1755 | my $rowHTML = |
|---|
| 1756 | $doubleIndent . CGI::Tr( { class => $trClassName }, $rowtext ); |
|---|
| 1757 | |
|---|
| 1758 | my $isHeaderRow = |
|---|
| 1759 | $rowCount < |
|---|
| 1760 | $combinedTableAttrs->{headerrows}; #( $headerCellCount == $colCount ); |
|---|
| 1761 | my $isFooterRow = |
|---|
| 1762 | ( ( $numberOfRows - $rowCount ) <= |
|---|
| 1763 | $combinedTableAttrs->{footerrows} ); |
|---|
| 1764 | |
|---|
| 1765 | if ( !$isHeaderRow && !$isFooterRow ) { |
|---|
| 1766 | |
|---|
| 1767 | # don't include non-adjacent header rows to the top block of header rows |
|---|
| 1768 | $isPastHeaderRows = 1; |
|---|
| 1769 | } |
|---|
| 1770 | |
|---|
| 1771 | if ($isFooterRow) { |
|---|
| 1772 | push @footerRowList, $rowHTML; |
|---|
| 1773 | } |
|---|
| 1774 | elsif ( $isHeaderRow && !$isPastHeaderRows ) { |
|---|
| 1775 | push( @headerRowList, $rowHTML ); |
|---|
| 1776 | } |
|---|
| 1777 | else { |
|---|
| 1778 | push @bodyRowList, $rowHTML; |
|---|
| 1779 | $dataColorCount++; |
|---|
| 1780 | } |
|---|
| 1781 | |
|---|
| 1782 | if ($isHeaderRow) { |
|---|
| 1783 | |
|---|
| 1784 | # reset data color count to start with first color after |
|---|
| 1785 | # each table heading |
|---|
| 1786 | $dataColorCount = 0; |
|---|
| 1787 | } |
|---|
| 1788 | |
|---|
| 1789 | $rowCount++; |
|---|
| 1790 | } # foreach my $row ( @curTable ) |
|---|
| 1791 | |
|---|
| 1792 | my $thead = |
|---|
| 1793 | "$singleIndent<thead>" |
|---|
| 1794 | . join( "", @headerRowList ) |
|---|
| 1795 | . "$singleIndent</thead>"; |
|---|
| 1796 | $text .= $currTablePre . $thead if scalar @headerRowList; |
|---|
| 1797 | |
|---|
| 1798 | my $tfoot = |
|---|
| 1799 | "$singleIndent<tfoot>" |
|---|
| 1800 | . join( "", @footerRowList ) |
|---|
| 1801 | . "$singleIndent</tfoot>"; |
|---|
| 1802 | $text .= $currTablePre . $tfoot if scalar @footerRowList; |
|---|
| 1803 | |
|---|
| 1804 | my $tbody; |
|---|
| 1805 | if ( scalar @bodyRowList ) { |
|---|
| 1806 | $tbody = |
|---|
| 1807 | "$singleIndent<tbody>" |
|---|
| 1808 | . join( "", @bodyRowList ) |
|---|
| 1809 | . "$singleIndent</tbody>"; |
|---|
| 1810 | } |
|---|
| 1811 | else { |
|---|
| 1812 | |
|---|
| 1813 | # A HTML table requires a body, which cannot be empty (Item8991). |
|---|
| 1814 | # So we provide one, but prevent it from being displayed. |
|---|
| 1815 | $tbody = |
|---|
| 1816 | "$singleIndent<tbody>$doubleIndent<tr style=\"display:none;\">$tripleIndent<td></td>$doubleIndent</tr>$singleIndent</tbody>\n"; |
|---|
| 1817 | } |
|---|
| 1818 | |
|---|
| 1819 | if ( scalar @messages ) { |
|---|
| 1820 | $text = |
|---|
| 1821 | '<span class="foswikiAlert">' |
|---|
| 1822 | . Foswiki::Func::expandCommonVariables( join( "\n", @messages ) ) |
|---|
| 1823 | . '</span>' . "\n" |
|---|
| 1824 | . $text; |
|---|
| 1825 | } |
|---|
| 1826 | |
|---|
| 1827 | $text .= $currTablePre . $tbody; |
|---|
| 1828 | $text .= $currTablePre . CGI::end_table() . "\n"; |
|---|
| 1829 | |
|---|
| 1830 | return $text; |
|---|
| 1831 | } |
|---|
| 1832 | |
|---|
| 1833 | sub handler { |
|---|
| 1834 | ### my ( $text, $removed ) = @_; |
|---|
| 1835 | |
|---|
| 1836 | _debug('handler'); |
|---|
| 1837 | |
|---|
| 1838 | unless ($Foswiki::Plugins::TablePlugin::initialised) { |
|---|
| 1839 | $insideTABLE = 0; |
|---|
| 1840 | |
|---|
| 1841 | # Even if $tableCount is initialized already at plugin init |
|---|
| 1842 | # we need to reset it again each time preRenderingHandler |
|---|
| 1843 | # calls this handler sub. Important for initialiseWhenRender API |
|---|
| 1844 | $tableCount = 0; |
|---|
| 1845 | |
|---|
| 1846 | my $cgi = Foswiki::Func::getCgiQuery(); |
|---|
| 1847 | return unless $cgi; |
|---|
| 1848 | |
|---|
| 1849 | # Copy existing values |
|---|
| 1850 | my ( @origSort, @origTable, @origUp ); |
|---|
| 1851 | @origSort = $cgi->param('sortcol'); |
|---|
| 1852 | @origTable = $cgi->param('table'); |
|---|
| 1853 | @origUp = $cgi->param('up'); # NOTE: internal parameter |
|---|
| 1854 | $cgi->delete( 'sortcol', 'table', 'up' ); |
|---|
| 1855 | $url = $cgi->url( -absolute => 1, -path => 1 ) . '?'; |
|---|
| 1856 | my $queryString = $cgi->query_string(); |
|---|
| 1857 | $url .= $queryString . ';' if $queryString; |
|---|
| 1858 | |
|---|
| 1859 | # Restore parameters, so we don't interfere on the remaining execution |
|---|
| 1860 | $cgi->param( -name => 'sortcol', -value => \@origSort ) if @origSort; |
|---|
| 1861 | $cgi->param( -name => 'table', -value => \@origTable ) if @origTable; |
|---|
| 1862 | $cgi->param( -name => 'up', -value => \@origUp ) if @origUp; |
|---|
| 1863 | |
|---|
| 1864 | $sortColFromUrl = |
|---|
| 1865 | $cgi->param('sortcol'); # zero based: 0 is first column |
|---|
| 1866 | if ( defined $sortColFromUrl && $sortColFromUrl !~ m/^[0-9]+$/ ) { |
|---|
| 1867 | $sortColFromUrl = 0; |
|---|
| 1868 | } |
|---|
| 1869 | |
|---|
| 1870 | $requestedTable = $cgi->param('table'); |
|---|
| 1871 | $requestedTable = 0 |
|---|
| 1872 | unless ( defined $requestedTable && $requestedTable =~ m/^[0-9]+$/ ); |
|---|
| 1873 | |
|---|
| 1874 | $up = $cgi->param('up'); |
|---|
| 1875 | |
|---|
| 1876 | $sortTablesInText = 0; |
|---|
| 1877 | $sortAttachments = 0; |
|---|
| 1878 | my $tmp = Foswiki::Func::getPreferencesValue('TABLEPLUGIN_SORT') |
|---|
| 1879 | || 'all'; |
|---|
| 1880 | if ( !$tmp || $tmp =~ /^all$/oi ) { |
|---|
| 1881 | $sortTablesInText = 1; |
|---|
| 1882 | $sortAttachments = 1; |
|---|
| 1883 | } |
|---|
| 1884 | elsif ( $tmp =~ /^attachments$/oi ) { |
|---|
| 1885 | $sortAttachments = 1; |
|---|
| 1886 | } |
|---|
| 1887 | |
|---|
| 1888 | _initDefaults(); # first time |
|---|
| 1889 | $Foswiki::Plugins::TablePlugin::initialised = 1; |
|---|
| 1890 | } |
|---|
| 1891 | |
|---|
| 1892 | $insideTABLE = 0; |
|---|
| 1893 | |
|---|
| 1894 | my $defaultSort = $combinedTableAttrs->{sortAllTables}; |
|---|
| 1895 | |
|---|
| 1896 | my $acceptable = $combinedTableAttrs->{sortAllTables}; |
|---|
| 1897 | my @lines = split( /\r?\n/, $_[0] ); |
|---|
| 1898 | for (@lines) { |
|---|
| 1899 | if ( |
|---|
| 1900 | s/$PATTERN_TABLE/_parseTableSpecificTableAttributes(Foswiki::Func::extractParameters($1))/se |
|---|
| 1901 | ) |
|---|
| 1902 | { |
|---|
| 1903 | $acceptable = 1; |
|---|
| 1904 | } |
|---|
| 1905 | elsif (s/^(\s*)\|(.*\|\s*)$/_processTableRow($1,$2)/eo) { |
|---|
| 1906 | $insideTABLE = 1; |
|---|
| 1907 | } |
|---|
| 1908 | elsif ($insideTABLE) { |
|---|
| 1909 | $_ = emitTable() . $_; |
|---|
| 1910 | $insideTABLE = 0; |
|---|
| 1911 | |
|---|
| 1912 | $combinedTableAttrs->{sortAllTables} = $defaultSort; |
|---|
| 1913 | $acceptable = $defaultSort; |
|---|
| 1914 | |
|---|
| 1915 | # prepare for next table |
|---|
| 1916 | _resetReusedVariables(); |
|---|
| 1917 | } |
|---|
| 1918 | } |
|---|
| 1919 | $_[0] = join( "\n", @lines ); |
|---|
| 1920 | |
|---|
| 1921 | if ($insideTABLE) { |
|---|
| 1922 | $_[0] .= emitTable(); |
|---|
| 1923 | } |
|---|
| 1924 | |
|---|
| 1925 | # prepare for next table |
|---|
| 1926 | _resetReusedVariables(); |
|---|
| 1927 | } |
|---|
| 1928 | |
|---|
| 1929 | =pod |
|---|
| 1930 | |
|---|
| 1931 | _mergeHashes (\%a, \%b ) -> \%merged |
|---|
| 1932 | |
|---|
| 1933 | Merges 2 hash references. |
|---|
| 1934 | |
|---|
| 1935 | =cut |
|---|
| 1936 | |
|---|
| 1937 | sub _mergeHashes { |
|---|
| 1938 | my ( $A, $B ) = @_; |
|---|
| 1939 | |
|---|
| 1940 | my %merged = (); |
|---|
| 1941 | while ( my ( $k, $v ) = each(%$A) ) { |
|---|
| 1942 | $merged{$k} = $v; |
|---|
| 1943 | } |
|---|
| 1944 | while ( my ( $k, $v ) = each(%$B) ) { |
|---|
| 1945 | $merged{$k} = $v; |
|---|
| 1946 | } |
|---|
| 1947 | return \%merged; |
|---|
| 1948 | } |
|---|
| 1949 | |
|---|
| 1950 | =pod |
|---|
| 1951 | |
|---|
| 1952 | =cut |
|---|
| 1953 | |
|---|
| 1954 | sub _cleanParamValue { |
|---|
| 1955 | my ($inValue) = @_; |
|---|
| 1956 | |
|---|
| 1957 | return undef if !$inValue; |
|---|
| 1958 | |
|---|
| 1959 | $inValue =~ s/ //go; # remove spaces |
|---|
| 1960 | return $inValue; |
|---|
| 1961 | } |
|---|
| 1962 | |
|---|
| 1963 | =pod |
|---|
| 1964 | |
|---|
| 1965 | =cut |
|---|
| 1966 | |
|---|
| 1967 | sub _arrayRefFromParam { |
|---|
| 1968 | my ($inValue) = @_; |
|---|
| 1969 | |
|---|
| 1970 | return undef if !$inValue; |
|---|
| 1971 | |
|---|
| 1972 | $inValue =~ s/ //go; # remove spaces |
|---|
| 1973 | my @list = split( /,/, $inValue ); |
|---|
| 1974 | return \@list; |
|---|
| 1975 | } |
|---|
| 1976 | |
|---|
| 1977 | =pod |
|---|
| 1978 | |
|---|
| 1979 | Shorthand debugging call. |
|---|
| 1980 | |
|---|
| 1981 | =cut |
|---|
| 1982 | |
|---|
| 1983 | sub _debug { |
|---|
| 1984 | return Foswiki::Plugins::TablePlugin::debug( 'TablePlugin::Core', @_ ); |
|---|
| 1985 | } |
|---|
| 1986 | |
|---|
| 1987 | sub _debugData { |
|---|
| 1988 | return Foswiki::Plugins::TablePlugin::debugData( 'TablePlugin::Core', @_ ); |
|---|
| 1989 | } |
|---|
| 1990 | |
|---|
| 1991 | 1; |
|---|
| 1992 | __END__ |
|---|
| 1993 | Foswiki - The Free and Open Source Wiki, http://foswiki.org/ |
|---|
| 1994 | |
|---|
| 1995 | Copyright (C) 2008-2012 Foswiki Contributors. Foswiki Contributors |
|---|
| 1996 | are listed in the AUTHORS file in the root of this distribution. |
|---|
| 1997 | NOTE: Please extend that file, not this notice. |
|---|
| 1998 | |
|---|
| 1999 | Additional copyrights apply to some or all of the code in this |
|---|
| 2000 | file as follows: |
|---|
| 2001 | |
|---|
| 2002 | Copyright (C) 2005-2006 TWiki Contributors |
|---|
| 2003 | Copyright (C) 2001-2004 Peter Thoeny, peter@thoeny.org |
|---|
| 2004 | Copyright (C) 2001-2003 John Talintyre, jet@cheerful.com |
|---|
| 2005 | |
|---|
| 2006 | This program is free software; you can redistribute it and/or |
|---|
| 2007 | modify it under the terms of the GNU General Public License |
|---|
| 2008 | as published by the Free Software Foundation; either version 2 |
|---|
| 2009 | of the License, or (at your option) any later version. For |
|---|
| 2010 | more details read LICENSE in the root of this distribution. |
|---|
| 2011 | |
|---|
| 2012 | This program is distributed in the hope that it will be useful, |
|---|
| 2013 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 2014 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
|---|
| 2015 | |
|---|
| 2016 | As per the GPL, removal of this notice is prohibited. |
|---|