(file) Return to SpreadSheetPlugin.pm CVS log (file) (dir) Up to [RizwankCVS] / geekymedia_web / twiki / lib / TWiki / Plugins

   1 rizwank 1.1 #
   2             # TWiki WikiClone ($wikiversion has version info)
   3             #
   4             # Copyright (C) 2001-2004 Peter Thoeny, Peter@Thoeny.com
   5             #
   6             # This program is free software; you can redistribute it and/or
   7             # modify it under the terms of the GNU General Public License
   8             # as published by the Free Software Foundation; either version 2
   9             # of the License, or (at your option) any later version.
  10             #
  11             # This program is distributed in the hope that it will be useful,
  12             # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14             # GNU General Public License for more details, published at
  15             # http://www.gnu.org/copyleft/gpl.html
  16             #
  17             # =========================
  18             #
  19             # This is the spreadsheet TWiki plugin.
  20             #
  21             # Each plugin is a package that contains the subs:
  22 rizwank 1.1 #
  23             #   initPlugin           ( $topic, $web, $user )
  24             #   commonTagsHandler    ( $text, $topic, $web )
  25             #   startRenderingHandler( $text, $web )
  26             #   outsidePREHandler    ( $text )
  27             #   insidePREHandler     ( $text )
  28             #   endRenderingHandler  ( $text )
  29             #
  30             # initPlugin is required, all other are optional.
  31             # For increased performance, DISABLE handlers you don't need.
  32             
  33             # =========================
  34             package TWiki::Plugins::SpreadSheetPlugin;
  35             
  36             use Time::Local;
  37             
  38             
  39             # =========================
  40             use vars qw(
  41                     $web $topic $user $installWeb $VERSION $debug $skipInclude $dontSpaceRE
  42                     $renderingWeb @tableMatrix $cPos $rPos $escToken
  43 rizwank 1.1         %varStore @monArr @wdayArr %mon2num
  44                 );
  45             
  46             $VERSION = '1.016';  # 23 Oct 2004
  47             $escToken = "\0";
  48             %varStore = ();
  49             $dontSpaceRE = "";
  50             @monArr = ( "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" );
  51             @wdayArr = ( "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" );
  52             { my $count = 0;
  53               %mon2num = map { $_ => $count++ } @monArr;
  54             }
  55             
  56             
  57             # =========================
  58             sub initPlugin
  59             {
  60                 ( $topic, $web, $user, $installWeb ) = @_;
  61             
  62                 # check for Plugins.pm versions
  63                 if( $TWiki::Plugins::VERSION < 1 ) {
  64 rizwank 1.1         &TWiki::Func::writeWarning( "Version mismatch between SpreadSheetPlugin and Plugins.pm" );
  65                     return 0;
  66                 }
  67             
  68                 $renderingWeb = $web;
  69             
  70                 # Get plugin debug flag
  71                 $debug = &TWiki::Func::getPreferencesFlag( "SPREADSHEETPLUGIN_DEBUG" );
  72             
  73                 # Flag to skip calc if in include
  74                 $skipInclude = &TWiki::Func::getPreferencesFlag( "SPREADSHEETPLUGIN_SKIPINCLUDE" );
  75             
  76                 # initialize variables
  77                 %varStore = ();
  78                 $dontSpaceRE = "";
  79             
  80                 # Plugin correctly initialized
  81                 &TWiki::Func::writeDebug( "- TWiki::Plugins::SpreadSheetPlugin::initPlugin( $web.$topic ) is OK" ) if $debug;
  82                 return 1;
  83             }
  84             
  85 rizwank 1.1 # =========================
  86             sub commonTagsHandler
  87             {
  88             ### my ( $text, $topic, $web ) = @_;   # do not uncomment, use $_[0], $_[1]... instead
  89             
  90                 &TWiki::Func::writeDebug( "- SpreadSheetPlugin::commonTagsHandler( $_[2].$_[1] )" ) if $debug;
  91             
  92                 if( ( $_[3] ) && ( $skipInclude ) ) {
  93                     # bail out, handler called from an %INCLUDE{}%
  94                     return;
  95                 }
  96                 unless( $_[0] =~ /%CALC\{.*?\}%/ ) {
  97                     # nothing to do
  98                     return;
  99                 }
 100             
 101                 @tableMatrix = ();
 102                 $cPos = -1;
 103                 $rPos = -1;
 104             
 105                 my $result = "";
 106 rizwank 1.1     my $insidePRE = 0;
 107                 my $insideTABLE = 0;
 108                 my $line = "";
 109                 my $before = "";
 110                 my $cell = "";
 111                 my @row = ();
 112             
 113                 $_[0] =~ s/\r//go;
 114                 $_[0] =~ s/\\\n//go;  # Join lines ending in "\"
 115                 foreach( split( /\n/, $_[0] ) ) {
 116             
 117                     # change state:
 118                     m|<pre>|i       && ( $insidePRE = 1 );
 119                     m|<verbatim>|i  && ( $insidePRE = 1 );
 120                     m|</pre>|i      && ( $insidePRE = 0 );
 121                     m|</verbatim>|i && ( $insidePRE = 0 );
 122             
 123                     if( ! ( $insidePRE ) ) {
 124             
 125                         if( /^\s*\|.*\|\s*$/ ) {
 126                             # inside | table |
 127 rizwank 1.1                 if( ! $insideTABLE ) {
 128                                 $insideTABLE = 1;
 129                                 @tableMatrix = ();  # reset table matrix
 130                                 $cPos = -1;
 131                                 $rPos = -1;
 132                             }
 133                             $line = $_;
 134                             $line =~ s/^(\s*\|)(.*)\|\s*$/$2/o;
 135                             $before = $1;
 136                             @row  = split( /\|/o, $line, -1 );
 137                             push @tableMatrix, [ @row ];
 138                             $rPos++;
 139                             $line = "$before";
 140                             for( $cPos = 0; $cPos < @row; $cPos++ ) {
 141                                 $cell = $row[$cPos];
 142                                 $cell =~ s/%CALC\{(.*?)\}%/&doCalc($1)/geo;
 143                                 $line .= "$cell|";
 144                             }
 145                             s/.*/$line/o;
 146             
 147                         } else {
 148 rizwank 1.1                 # outside | table |
 149                             if( $insideTABLE ) {
 150                                 $insideTABLE = 0;
 151                             }
 152                             s/%CALC\{(.*?)\}%/&doCalc($1)/geo;
 153                         }
 154                     }
 155                     $result .= "$_\n";
 156                 }
 157                 $_[0] = $result;
 158             }
 159             
 160             # =========================
 161             sub doCalc
 162             {
 163                 my( $theAttributes ) = @_;
 164                 my $text = &TWiki::Func::extractNameValuePair( $theAttributes );
 165             
 166                 # Add nesting level to parenthesis,
 167                 # e.g. "A(B())" gets "A-esc-1(B-esc-2(-esc-2)-esc-1)"
 168                 $text =~ s/([\(\)])/addNestingLevel($1, \$level)/geo;
 169 rizwank 1.1     $text = doFunc( "MAIN", $text );
 170             
 171                 if( ( $rPos >= 0 ) && ( $cPos >= 0 ) ) {
 172                     # update cell in table matrix
 173                     $tableMatrix[$rPos][$cPos] = $text;
 174                 }
 175             
 176                 return $text;
 177             }
 178             
 179             # =========================
 180             sub addNestingLevel
 181             {
 182               my( $theParen, $theLevelRef ) = @_;
 183             
 184               my $result = "";
 185               if( $theParen eq "(" ) {
 186                 $$theLevelRef++;
 187                 $result = "$escToken$$theLevelRef$theParen";
 188               } else {
 189                 $result = "$escToken$$theLevelRef$theParen";
 190 rizwank 1.1     $$theLevelRef--;
 191               }
 192               return $result;
 193             }
 194             
 195             # =========================
 196             sub doFunc
 197             {
 198                 my( $theFunc, $theAttr ) = @_;
 199             
 200                 $theAttr = "" unless( defined $theAttr );
 201                 &TWiki::Func::writeDebug( "- SpreadSheetPlugin::doFunc: $theFunc( $theAttr ) start" ) if $debug;
 202             
 203                 unless( $theFunc =~ /^(IF|LISTIF|LISTMAP)$/ ) {
 204                     # Handle functions recursively
 205                     $theAttr =~ s/\$([A-Z]+)$escToken([0-9]+)\((.*?)$escToken\2\)/&doFunc($1,$3)/geo;
 206                     # Clean up unbalanced mess
 207                     $theAttr =~ s/$escToken\-*[0-9]+([\(\)])/$1/go;
 208                 }
 209                 # else: delay the function handler to after parsing the parameters,
 210                 # in which case handling functions and cleaning up needs to be done later
 211 rizwank 1.1 
 212                 my $result = "";
 213                 my $i = 0;
 214                 if( $theFunc eq "MAIN" ) {
 215                     $result = $theAttr;
 216             
 217                 } elsif( $theFunc eq "T" ) {
 218                     $result = "";
 219                     my @arr = getTableRange( "$theAttr..$theAttr" );
 220                     if( @arr ) {
 221                         $result = $arr[0];
 222                     }
 223             
 224                 } elsif( $theFunc eq "TRIM" ) {
 225                     $result = $theAttr || "";
 226                     $result =~ s/^\s*//o;
 227                     $result =~ s/\s*$//o;
 228                     $result =~ s/\s+/ /go;
 229             
 230                 } elsif( $theFunc eq "FORMAT" ) {
 231                     # Format FORMAT(TYPE, precision, value) returns formatted value -- JimStraus - 05 Jan 2003
 232 rizwank 1.1         my( $format, $res, $value )  = split( /,\s*/, $theAttr );
 233                     $format =~ s/^\s*(.*?)\s*$/$1/; #Strip leading and trailing spaces
 234                     $res =~ s/^\s*(.*?)\s*$/$1/;
 235                     $value =~ s/^\s*(.*?)\s*$/$1/;
 236                     if( $format eq "DOLLAR" ) {
 237                         my $neg = 1 if $value < 0;
 238                         $value = abs($value);
 239                         $result = sprintf("%0.${res}f", $value);
 240                         my $temp = reverse $result;
 241                         $temp =~ s/(\d\d\d)(?=\d)(?!\d*\.)/$1,/g;
 242                         $result = "\$" . (scalar reverse $temp);
 243                         $result = "(".$result.")" if $neg;
 244                     } elsif( $format eq "COMMA" ) {
 245                         $result = sprintf("%0.${res}f", $value);
 246                         my $temp = reverse $result;
 247                         $temp =~ s/(\d\d\d)(?=\d)(?!\d*\.)/$1,/g;
 248                         $result = scalar reverse $temp;
 249                     } elsif( $format eq "PERCENT" ) {
 250                         $result = sprintf("%0.${res}f%%", $value * 100);
 251                     } elsif( $format eq "NUMBER" ) {
 252                         $result = sprintf("%0.${res}f", $value);
 253 rizwank 1.1         } elsif( $format eq "K" ) {
 254                         $result = sprintf("%0.${res}f K", $value / 1024);
 255                     } elsif( $format eq "KB" ) {
 256                         $result = sprintf("%0.${res}f KB", $value / 1024);
 257                     } elsif ($format eq "MB") {
 258                         $result = sprintf("%0.${res}f MB", $value / (1024 * 1024));
 259                     } elsif( $format =~ /^KBMB/ ) {
 260                         $value /= 1024;
 261                         my @lbls = ( "MB", "GB", "TB", "PB", "EB", "ZB" );
 262                         my $lbl = "KB";
 263                         while( $value >= 1024 && @lbls ) {
 264                             $value /= 1024;
 265                             $lbl = shift @lbls;
 266                         }
 267                         $result = sprintf("%0.${res}f", $value) . " $lbl";
 268                     } else {
 269                         # FORMAT not recognized, just return value
 270                         $result = $value;
 271                     }
 272             
 273                 } elsif( $theFunc eq "EXACT" ) {
 274 rizwank 1.1         $result = 0;
 275                     my( $str1, $str2 ) = split( /,\s*/, $theAttr, 2 );
 276                     $str1 = "" unless( $str1 );
 277                     $str2 = "" unless( $str2 );
 278                     $str1 =~ s/^\s*(.*?)\s*$/$1/o; # cut leading and trailing spaces
 279                     $str2 =~ s/^\s*(.*?)\s*$/$1/o;
 280                     $result = 1 if( $str1 eq $str2 );
 281             
 282                 } elsif( $theFunc eq "RAND" ) {
 283                     my $max = _getNumber( $theAttr );
 284                     $max = 1 if( $max <= 0 );
 285                     $result = rand( $max );
 286             
 287                 } elsif( $theFunc eq "VALUE" ) {
 288                     $result = _getNumber( $theAttr );
 289             
 290                 } elsif( $theFunc =~ /^(EVAL|INT)$/ ) {
 291                     $result = safeEvalPerl( $theAttr );
 292                     unless( $result =~ /^ERROR/ ) {
 293                         $result = int( _getNumber( $result ) ) if( $theFunc eq "INT" );
 294                     }
 295 rizwank 1.1 
 296                 } elsif( $theFunc eq "ROUND" ) {
 297                     # ROUND(num, digits)
 298                     my( $num, $digits ) = split( /,\s*/, $theAttr, 2 );
 299                     $result = safeEvalPerl( $num );
 300                     unless( $result =~ /^ERROR/ ) {
 301                         $result = _getNumber( $result );
 302                         if( ( $digits ) && ( $digits =~ s/^.*?(\-?[0-9]+).*$/$1/o ) && ( $digits ) ) {
 303                             my $factor = 10**$digits;
 304                             $result *= $factor;
 305                             ( $result >= 0 ) ? ( $result += 0.5 ) : ( $result -= 0.5 );
 306                             $result = int( $result );
 307                             $result /= $factor;
 308                         } else {
 309                             ( $result >= 0 ) ? ( $result += 0.5 ) : ( $result -= 0.5 );
 310                             $result = int( $result );
 311                         }
 312                     }
 313             
 314                 } elsif( $theFunc eq "MOD" ) {
 315                     $result = 0;
 316 rizwank 1.1         my( $num1, $num2 ) = split( /,\s*/, $theAttr, 2 );
 317                     $num1 = _getNumber( $num1 );
 318                     $num2 = _getNumber( $num2 );
 319                     if( $num1 && $num2 ) {
 320                         $result = $num1 % $num2;
 321                     }
 322             
 323                 } elsif( $theFunc eq "ODD" ) {
 324                     $result = _getNumber( $theAttr ) % 2;
 325             
 326                 } elsif( $theFunc eq "EVEN" ) {
 327                     $result = ( _getNumber( $theAttr ) + 1 ) % 2;
 328             
 329                 } elsif( $theFunc eq "AND" ) {
 330                     $result = 0;
 331                     my @arr = getListAsInteger( $theAttr );
 332                     foreach $i( @arr ) {
 333                         unless( $i ) {
 334                             $result = 0;
 335                             last;
 336                         }
 337 rizwank 1.1             $result = 1;
 338                     }
 339             
 340                 } elsif( $theFunc eq "OR" ) {
 341                     $result = 0;
 342                     my @arr = getListAsInteger( $theAttr );
 343                     foreach $i( @arr ) {
 344                         if( $i ) {
 345                             $result = 1;
 346                             last;
 347                         }
 348                     }
 349             
 350                 } elsif( $theFunc eq "NOT" ) {
 351                     $result = 1;
 352                     $result = 0 if( _getNumber( $theAttr ) );
 353             
 354                 } elsif( $theFunc eq "ABS" ) {
 355                     $result = abs( _getNumber( $theAttr ) );
 356             
 357                 } elsif( $theFunc eq "SIGN" ) {
 358 rizwank 1.1         $i = _getNumber( $theAttr );
 359                     $result =  0;
 360                     $result =  1 if( $i > 0 );
 361                     $result = -1 if( $i < 0 );
 362             
 363                 } elsif( $theFunc eq "IF" ) {
 364                     # IF(condition, value if true, value if false)
 365                     my( $condition, $str1, $str2 ) = _properSplit( $theAttr, 3 );
 366                     # with delay, handle functions in condition recursively and clean up unbalanced parenthesis
 367                     $condition =~ s/\$([A-Z]+)$escToken([0-9]+)\((.*?)$escToken\2\)/&doFunc($1,$3)/geo;
 368                     $condition =~ s/$escToken\-*[0-9]+([\(\)])/$1/go;
 369                     $condition =~ s/^\s*(.*?)\s*$/$1/o;
 370                     $result = safeEvalPerl( $condition );
 371                     unless( $result =~ /^ERROR/ ) {
 372                         if( $result ) {
 373                             $result = $str1;
 374                         } else {
 375                             $result = $str2;
 376                         }
 377                         $result = "" unless( defined( $result ) );
 378                         # with delay, handle functions in result recursively and clean up unbalanced parenthesis
 379 rizwank 1.1             $result =~ s/\$([A-Z]+)$escToken([0-9]+)\((.*?)$escToken\2\)/&doFunc($1,$3)/geo;
 380                         $result =~ s/$escToken\-*[0-9]+([\(\)])/$1/go;
 381             
 382                     } # else return error message
 383             
 384                 } elsif( $theFunc eq "UPPER" ) {
 385                     $result = uc( $theAttr );
 386             
 387                 } elsif( $theFunc eq "LOWER" ) {
 388                     $result = lc( $theAttr );
 389             
 390                 } elsif( $theFunc eq "PROPER" ) {
 391                     # FIXME: I18N
 392                     $result = lc( $theAttr );
 393                     $result =~ s/(^|[^a-z])([a-z])/$1 . uc($2)/geo;
 394             
 395                 } elsif( $theFunc eq "PROPERSPACE" ) {
 396                     $result = _properSpace( $theAttr );
 397             
 398                 } elsif( $theFunc eq "CHAR" ) {
 399                     $theAttr =~ /([0-9]+)/;
 400 rizwank 1.1         $i = $1 || 0;
 401                     $i = 255 if $i > 255;
 402                     $i = 0 if $i < 0;
 403                     $result = chr( $i );
 404             
 405                 } elsif( $theFunc eq "REPEAT" ) {
 406                     my( $str, $num ) = split( /,\s*/, $theAttr, 2 );
 407                     $str = "" unless( defined( $str ) );
 408                     $num = _getNumber( $num );
 409                     $result = "$str" x $num;
 410             
 411                 } elsif( $theFunc eq "CODE" ) {
 412                     $result = ord( $theAttr );
 413             
 414                 } elsif( $theFunc eq "LENGTH" ) {
 415                     $result = length( $theAttr );
 416             
 417                 } elsif( $theFunc eq "ROW" ) {
 418                     $i = $theAttr || 0;
 419                     $result = $rPos + $i + 1;
 420             
 421 rizwank 1.1     } elsif( $theFunc eq "COLUMN" ) {
 422                     $i = $theAttr || 0;
 423                     $result = $cPos + $i + 1;
 424             
 425                 } elsif( $theFunc eq "LEFT" ) {
 426                     $i = $rPos + 1;
 427                     $result = "R$i:C0..R$i:C$cPos";
 428             
 429                 } elsif( $theFunc eq "ABOVE" ) {
 430                     $i = $cPos + 1;
 431                     $result = "R0:C$i..R$rPos:C$i";
 432             
 433                 } elsif( $theFunc eq "RIGHT" ) {
 434                     $i = $rPos + 1;
 435                     $result = "R$i:C$cPos..R$i:C32000";
 436             
 437                 } elsif( $theFunc eq "DEF" ) {
 438                     # Format DEF(list) returns first defined cell
 439                     # Added by MF 26/3/2002, fixed by PeterThoeny
 440                     my @arr = getList( $theAttr );
 441                     foreach my $cell ( @arr ) {
 442 rizwank 1.1             if( $cell ) {
 443                             $cell =~ s/^\s*(.*?)\s*$/$1/o;
 444                             if( $cell ) {
 445                                 $result = $cell;
 446                                 last;
 447                             }
 448                         }
 449                     }
 450             
 451                 } elsif( $theFunc eq "MAX" ) {
 452                     my @arr = sort { $a <=> $b }
 453                               grep { /./ }
 454                               grep { defined $_ }
 455                               getListAsFloat( $theAttr );
 456                     $result = $arr[$#arr];
 457             
 458                 } elsif( $theFunc eq "MIN" ) {
 459                     my @arr = sort { $a <=> $b }
 460                               grep { /./ }
 461                               grep { defined $_ }
 462                               getListAsFloat( $theAttr );
 463 rizwank 1.1         $result = $arr[0];
 464             
 465                 } elsif( $theFunc eq "SUM" ) {
 466                     $result = 0;
 467                     my @arr = getListAsFloat( $theAttr );
 468                     foreach $i ( @arr ) {
 469                         $result += $i  if defined $i;
 470                     }
 471             
 472                 } elsif( $theFunc eq "SUMPRODUCT" ) {
 473                     $result = 0;
 474                     my @arr;
 475                     my @lol = split( /,\s*/, $theAttr );
 476                     my $size = 32000;
 477                     for $i (0 .. $#lol ) {
 478                         @arr = getListAsFloat( $lol[$i] );
 479                         $lol[$i] = [ @arr ];                # store reference to array
 480                         $size = @arr if( @arr < $size );    # remember smallest array
 481                     }
 482                     if( ( $size > 0 ) && ( $size < 32000 ) ) {
 483                         my $y; my $prod; my $val;
 484 rizwank 1.1             $size--;
 485                         for $y (0 .. $size ) {
 486                             $prod = 1;
 487                             for $i (0 .. $#lol ) {
 488                                 $val = $lol[$i][$y];
 489                                 if( defined $val ) {
 490                                     $prod *= $val;
 491                                 } else {
 492                                     $prod = 0;   # don't count empty cells
 493                                 }
 494                             }
 495                             $result += $prod;
 496                         }
 497                     }
 498             
 499                 } elsif( $theFunc =~ /^(SUMDAYS|DURATION)$/ ) {
 500                     # DURATION is undocumented, is for SvenDowideit
 501                     # contributed by SvenDowideit - 07 Mar 2003; modified by PTh
 502                     $result = 0;
 503                     my @arr = getListAsDays( $theAttr );
 504                     foreach $i ( @arr ) {
 505 rizwank 1.1             $result += $i  if defined $i;
 506                     }
 507             
 508                 } elsif( $theFunc eq "WORKINGDAYS" ) {
 509                     my( $num1, $num2 ) = split( /,\s*/, $theAttr, 2 );
 510                     $result = _workingDays( _getNumber( $num1 ), _getNumber( $num2 ) );
 511             
 512                 } elsif( $theFunc =~ /^(MULT|PRODUCT)$/ ) {   # MULT is deprecated, no not remove
 513                     $result = 0;
 514                     my @arr = getListAsFloat( $theAttr );
 515                     $result = 1;
 516                     foreach $i ( @arr ) {
 517                         $result *= $i  if defined $i;
 518                     }
 519             
 520                 } elsif( $theFunc =~ /^(AVERAGE|MEAN)$/ ) {
 521                     $result = 0;
 522                     my $items = 0;
 523                     my @arr = getListAsFloat( $theAttr );
 524                     foreach $i ( @arr ) {
 525                         if( defined $i ) {
 526 rizwank 1.1                 $result += $i;
 527                             $items++;
 528                         }
 529                     }
 530                     if( $items > 0 ) {
 531                         $result = $result / $items;
 532                     }
 533             
 534                 } elsif( $theFunc eq "MEDIAN" ) {
 535                     my @arr = sort { $a <=> $b } grep { defined $_ } getListAsFloat( $theAttr );
 536                     $i = @arr;
 537                     if( ( $i % 2 ) > 0 ) {
 538                         $result = $arr[$i/2];
 539                     } elsif( $i ) {
 540                         $i /= 2;
 541                         $result = ( $arr[$i] + $arr[$i-1] ) / 2;
 542                     }
 543             
 544                 } elsif( $theFunc eq "PERCENTILE" ) {
 545                     my( $percentile, $set ) = split( /,\s*/, $theAttr, 2 );
 546                     my @arr = sort { $a <=> $b } grep { defined $_ } getListAsFloat( $set );
 547 rizwank 1.1         $result = 0;
 548             
 549                     my $size = scalar( @arr );
 550                     if( $size > 0 ) {
 551                         $i = $percentile / 100 * ( $size + 1 );
 552                         my $iInt = int( $i );
 553                         if( $i <= 1 ) {
 554                             $result = $arr[0];
 555                         } elsif( $i >= $size ) {
 556                             $result = $arr[$size-1];
 557                         } elsif( $i == $iInt ) {
 558                             $result = $arr[$i-1];
 559                         } else {
 560                             # interpolate beween neighbors # Example: $i = 7.25
 561                             my $r1 = $iInt + 1 - $i;       # 0.75 = 7 + 1 - 7.25
 562                             my $r2 = 1 - $r1;              # 0.25 = 1 - 0.75
 563                             my $x1 = $arr[$iInt-1];
 564                             my $x2 = $arr[$iInt];
 565                             $result = ($r1 * $x1) + ($r2 * $x2);
 566                         }
 567                     }
 568 rizwank 1.1 
 569                 } elsif( $theFunc eq "COUNTSTR" ) {
 570                     $result = 0;  # count any string
 571                     $i = 0;       # count string equal second attr
 572                     my $list = $theAttr;
 573                     my $str = "";
 574                     if( $theAttr =~ /^(.*),\s*(.*?)$/ ) {  # greedy match for last comma
 575                         $list = $1;
 576                         $str = $2;
 577                     }
 578                     $str =~ s/\s*$//o;
 579                     my @arr = getList( $list );
 580                     foreach my $cell ( @arr ) {
 581                         if( defined $cell ) {
 582                             $cell =~ s/^\s*(.*?)\s*$/$1/o;
 583                             $result++ if( $cell );
 584                             $i++ if( $cell eq $str );
 585                         }
 586                     }
 587                     $result = $i if( $str );
 588             
 589 rizwank 1.1     } elsif( $theFunc eq "COUNTITEMS" ) {
 590                     $result = "";
 591                     my @arr = getList( $theAttr );
 592                     my %items = ();
 593                     my $key = "";
 594                     foreach $key ( @arr ) {
 595                         $key =~ s/^\s*(.*?)\s*$/$1/o if( $key );
 596                         if( $key ) {
 597                             if( exists( $items{ $key } ) ) {
 598                                 $items{ $key }++;
 599                             } else {
 600                                 $items{ $key } = 1;
 601                             }
 602                         }
 603                     }
 604                     foreach $key ( sort keys %items ) {
 605                         $result .= "$key: $items{ $key }<br /> ";
 606                     }
 607                     $result =~ s|<br /> $||o;
 608             
 609                 } elsif( $theFunc =~ /^(FIND|SEARCH)$/ ) {
 610 rizwank 1.1         my( $searchString, $string, $pos ) = split( /,\s*/, $theAttr, 3 );
 611                     $result = 0;
 612                     $pos--;
 613                     $pos = 0 if( $pos < 0 );
 614                     pos( $string ) = $pos if( $pos );
 615                     $searchString = quotemeta( $searchString ) if( $theFunc eq "FIND" );
 616                     # using zero width lookahead '(?=...)' to keep pos at the beginning of match
 617                     if( eval '$string =~ m/(?=$searchString)/g' && $string ) {
 618                         $result = pos( $string ) + 1;
 619                     }
 620             
 621                 } elsif( $theFunc eq "REPLACE" ) {
 622                     my( $string, $start, $num, $replace ) = split ( /,\s*/, $theAttr, 4 );
 623                     $result = $string;
 624                     $start-- unless ($start < 1);
 625                     $num = 0 unless( $num );
 626                     $replace = "" unless( defined $replace );
 627                     if( eval 'substr( $string, $start, $num, $replace )' && $string ) {
 628                         $result = $string;
 629                     }
 630             
 631 rizwank 1.1     } elsif( $theFunc eq "SUBSTITUTE" ) {
 632                     my( $string, $from, $to, $inst, $options ) = split( /,\s*/, $theAttr );
 633                     $result = $string;
 634                     $to = "" unless( defined $to );
 635                     $from = quotemeta( $from ) unless( $options && $options =~ /r/i);
 636                     if( $inst ) {
 637                         # replace Nth instance
 638                         my $count = 0;
 639                         if( eval '$string =~ s/($from)/if( ++$count == $inst ) { $to; } else { $1; }/gex;' && $string ) {
 640                             $result = $string;
 641                         }
 642                     } else {
 643                         # global replace
 644                         if( eval '$string =~ s/$from/$to/g' && $string ) {
 645                             $result = $string;
 646                         }
 647                     }
 648             
 649                 } elsif( $theFunc eq "TRANSLATE" ) {
 650                     $result = $theAttr;
 651                     # greedy match for comma separated parameters (in case first parameter has embedded commas)
 652 rizwank 1.1         if( $theAttr =~ /^(.*)\,\s*(.+)\,\s*(.+)$/ ) {
 653                         my $string = $1 || "";
 654                         my $from = $2;
 655                         my $to   = $3;
 656                         $from =~ s/\$comma/,/g;  $from =~ s/\$sp/ /g;  $from = quotemeta( $from );
 657                         $to   =~ s/\$comma/,/g;  $to   =~ s/\$sp/ /g;  $to   = quotemeta( $to );
 658                         $from =~ s/([a-zA-Z0-9])\\\-([a-zA-Z0-9])/$1\-$2/g; # fix quotemeta (allow only ranges)
 659                         $to   =~ s/([a-zA-Z0-9])\\\-([a-zA-Z0-9])/$1\-$2/g;
 660                         $result = $string;
 661                         if( $string && eval "\$string =~ tr/$from/$to/" ) {
 662                             $result = $string;
 663                         }
 664                    }
 665             
 666                 } elsif ( $theFunc eq "TIME" ) {
 667                    $result = $theAttr;
 668                    $result =~ s/^\s+//o;
 669                    $result =~ s/\s+$//o;
 670                    if( $result ) {
 671                        $result = _date2serial( $result );
 672                    } else {
 673 rizwank 1.1            $result = time();
 674                    }
 675             
 676                 } elsif ( $theFunc eq "TODAY" ) {
 677                    $result = _date2serial( _serial2date( time(), '$year/$month/$day GMT', 1 ) );
 678             
 679                 } elsif( $theFunc =~ /^(FORMATTIME|FORMATGMTIME)$/ ) {
 680                    my( $time, $str ) = split( /,\s*/, $theAttr, 2 );
 681                    if( $time =~ /([0-9]+)/ ) {
 682                        $time = $1;
 683                    } else {
 684                        $time = time();
 685                    }
 686                    my $isGmt = 0;
 687                    $isGmt = 1 if( ( $str =~ m/ gmt/i ) || ( $theFunc eq "FORMATGMTIME" ) );
 688                    $result = _serial2date( $time, $str, $isGmt );
 689             
 690                 } elsif( $theFunc eq "TIMEADD" ) {
 691                    my( $time, $value, $scale ) = split( /,\s*/, $theAttr, 3 );
 692                    $time = 0 unless( $time );
 693                    $value = 0 unless( $value );
 694 rizwank 1.1        $scale = "" unless( $scale );
 695                    $time =~ s/.*?([0-9]+).*/$1/o || 0;
 696                    $value =~ s/.*?(\-?[0-9\.]+).*/$1/o || 0;
 697                    $value *= 60            if( $scale =~ /^min/i );
 698                    $value *= 3600          if( $scale =~ /^hou/i );
 699                    $value *= 3600*24       if( $scale =~ /^day/i );
 700                    $value *= 3600*24*7     if( $scale =~ /^week/i );
 701                    $value *= 3600*24*30.42 if( $scale =~ /^mon/i );  # FIXME: exact calc
 702                    $value *= 3600*24*365   if( $scale =~ /^year/i ); # FIXME: exact calc
 703                    $result = int( $time + $value );
 704             
 705                 } elsif( $theFunc eq "TIMEDIFF" ) {
 706                    my( $time1, $time2, $scale ) = split( /,\s*/, $theAttr, 3 );
 707                    $time1 = 0 unless( $time1 );
 708                    $time2 = 0 unless( $time2 );
 709                    $time1 =~ s/.*?([0-9]+).*/$1/o || 0;
 710                    $time2 =~ s/.*?([0-9]+).*/$1/o || 0;
 711                    $result = $time2 - $time1;
 712                    $result /= 60            if( $scale =~ /^min/i );
 713                    $result /= 3600          if( $scale =~ /^hou/i );
 714                    $result /= 3600*24       if( $scale =~ /^day/i );
 715 rizwank 1.1        $result /= 3600*24*7     if( $scale =~ /^week/i );
 716                    $result /= 3600*24*30.42 if( $scale =~ /^mon/i );  # FIXME: exact calc
 717                    $result /= 3600*24*365   if( $scale =~ /^year/i ); # FIXME: exact calc
 718             
 719                 } elsif( $theFunc eq "SET" ) {
 720                    my( $name, $value ) = split( /,\s*/, $theAttr, 2 );
 721                    $name =~ s/[^a-zA-Z0-9\_]//go;
 722                    if( $name && defined( $value ) ) {
 723                        $value =~ s/\s*$//o;
 724                        $varStore{ $name } = $value;
 725                    }
 726             
 727                 } elsif( $theFunc eq "SETM" ) {
 728                    my( $name, $value ) = split( /,\s*/, $theAttr, 2 );
 729                    $name =~ s/[^a-zA-Z0-9\_]//go;
 730                    if( $name ) {
 731                        my $old = $varStore{ $name };
 732                        $old = "" unless( defined( $old ) );
 733                        $value = safeEvalPerl( "$old $value" );
 734                        $varStore{ $name } = $value;
 735                    }
 736 rizwank 1.1 
 737                 } elsif( $theFunc eq "GET" ) {
 738                    my $name = $theAttr;
 739                    $name =~ s/[^a-zA-Z0-9\_]//go;
 740                    $result = $varStore{ $name } if( $name );
 741                    $result = "" unless( defined( $result ) );
 742             
 743                 } elsif( $theFunc eq "LIST" ) {
 744                     my @arr = getList( $theAttr );
 745                     $result = _listToDelimitedString( @arr );
 746             
 747                 } elsif( $theFunc eq "LISTITEM" ) {
 748                     my( $index, $str ) = _properSplit( $theAttr, 2 );
 749                     $index = _getNumber( $index );
 750                     $str = "" unless( defined( $str ) );
 751                     my @arr = getList( $str );
 752                     my $size = scalar @arr;
 753                     if( $index && $size ) {
 754                         $index-- if( $index > 0 );                 # documented index starts at 1
 755                         $index = $size + $index if( $index < 0 );  # start from back if negative
 756                         $result = $arr[$index] if( ( $index >= 0 ) && ( $index < $size ) );
 757 rizwank 1.1         }
 758             
 759                 } elsif( $theFunc eq "LISTJOIN" ) {
 760                     my( $sep, $str ) = _properSplit( $theAttr, 2 );
 761                     $str = "" unless( defined( $str ) );
 762                     $result = _listToDelimitedString( getList( $str ) );
 763                     $sep = ", " unless( $sep );
 764                     $sep =~ s/\$comma/,/go;
 765                     $sep =~ s/\$sp/ /go;
 766                     $sep =~ s/\$n/\n/go;
 767                     $result =~ s/, /$sep/go;
 768             
 769                 } elsif( $theFunc eq "LISTSIZE" ) {
 770                     my @arr = getList( $theAttr );
 771                     $result = scalar @arr;
 772             
 773                 } elsif( $theFunc eq "LISTSORT" ) {
 774                     my $isNumeric = 1;
 775                     my @arr = map {
 776                         s/^\s*//o;
 777                         s/\s*$//o;
 778 rizwank 1.1             $isNumeric = 0 unless( $_ =~ /^[\+\-]?[0-9\.]+$/ );
 779                         $_
 780                     } getList( $theAttr );
 781                     if( $isNumeric ) {
 782                         @arr = sort { $a <=> $b } @arr;
 783                     } else {
 784                         @arr = sort @arr;
 785                     }
 786                     $result = _listToDelimitedString( @arr );
 787             
 788                 } elsif( $theFunc eq "LISTREVERSE" ) {
 789                     my @arr = reverse getList( $theAttr );
 790                     $result = _listToDelimitedString( @arr );
 791             
 792                 } elsif( $theFunc eq "LISTUNIQUE" ) {
 793                     my %seen = ();
 794                     my @arr = grep { ! $seen{$_} ++ } getList( $theAttr );
 795                     $result = _listToDelimitedString( @arr );
 796             
 797                 } elsif( $theFunc eq "LISTMAP" ) {
 798                     # LISTMAP(action, item 1, item 2, ...)
 799 rizwank 1.1         my( $action, $str ) = _properSplit( $theAttr, 2 );
 800                     $action = "" unless( defined( $action ) );
 801                     $str = "" unless( defined( $str ) );
 802                     # with delay, handle functions in result recursively and clean up unbalanced parenthesis
 803                     $str =~ s/\$([A-Z]+)$escToken([0-9]+)\((.*?)$escToken\2\)/&doFunc($1,$3)/geo;
 804                     $str =~ s/$escToken\-*[0-9]+([\(\)])/$1/go;
 805                     my $item = "";
 806                     $i = 0;
 807                     my @arr =
 808                         map {
 809                            $item = $_;
 810                            $_ = $action;
 811                            $i++;
 812                            s/\$index/$i/go;
 813                            $_ .= $item unless( s/\$item/$item/go );
 814                            s/\$([A-Z]+)$escToken([0-9]+)\((.*?)$escToken\2\)/&doFunc($1,$3)/geo;
 815                            s/$escToken\-*[0-9]+([\(\)])/$1/go;
 816                            $_
 817                         } getList( $str );
 818                     $result = _listToDelimitedString( @arr );
 819             
 820 rizwank 1.1     } elsif( $theFunc eq "LISTIF" ) {
 821                     # LISTIF(cmd, item 1, item 2, ...)
 822                     my( $cmd, $str ) = _properSplit( $theAttr, 2 );
 823                     $cmd = "" unless( defined( $cmd ) );
 824                     $cmd =~ s/^\s*(.*?)\s*$/$1/o;
 825                     $str = "" unless( defined( $str ) );
 826                     # with delay, handle functions in result recursively and clean up unbalanced parenthesis
 827                     $str =~ s/\$([A-Z]+)$escToken([0-9]+)\((.*?)$escToken\2\)/&doFunc($1,$3)/geo;
 828                     $str =~ s/$escToken\-*[0-9]+([\(\)])/$1/go;
 829                     my $item = "";
 830                     my $eval = "";
 831                     $i = 0;
 832                     my @arr =
 833                         grep { ! /^TWIKI_GREP_REMOVE$/ }
 834                         map {
 835                             $item = $_;
 836                             $_ = $cmd;
 837                             $i++;
 838                             s/\$index/$i/go;
 839                             s/\$item/$item/go;
 840                             s/\$([A-Z]+)$escToken([0-9]+)\((.*?)$escToken\2\)/&doFunc($1,$3)/geo;
 841 rizwank 1.1                 s/$escToken\-*[0-9]+([\(\)])/$1/go;
 842                             $eval = safeEvalPerl( $_ );
 843                             if( $eval =~ /^ERROR/ ) {
 844                                 $_ = $eval;
 845                             } elsif( $eval ) {
 846                                 $_ = $item;
 847                             } else {
 848                                 $_ = "TWIKI_GREP_REMOVE";
 849                             }
 850                         } getList( $str );
 851                     $result = _listToDelimitedString( @arr );
 852             
 853                 } elsif ( $theFunc eq "NOP" ) {
 854                     # pass everything through, this will allow plugins to defy plugin order
 855                     # for example the %SEARCH{}% variable
 856                     $theAttr =~ s/\$per/%/g;
 857                     $result = $theAttr;
 858             
 859                 } elsif ( $theFunc eq "EXISTS" ) {
 860                     $result = TWiki::Func::topicExists( "", $theAttr );
 861                     $result = 0 unless( $result );
 862 rizwank 1.1     }
 863             
 864                 &TWiki::Func::writeDebug( "- SpreadSheetPlugin::doFunc: $theFunc( $theAttr ) returns: $result" ) if $debug;
 865                 return $result;
 866             }
 867             
 868             # =========================
 869             sub _listToDelimitedString
 870             {
 871                 my @arr = map { s/^\s*//o; s/\s*$//o; $_ } @_;
 872                 my $text = join( ", ", @arr );
 873                 return $text;
 874             }
 875             
 876             # =========================
 877             sub _properSplit
 878             {
 879                 my( $theAttr, $theLevel ) = @_;
 880             
 881                 # escape commas inside functions
 882                 $theAttr =~ s/(\$[A-Z]+$escToken([0-9]+)\(.*?$escToken\2\))/_escapeCommas($1)/geo;
 883 rizwank 1.1     # split at commas and restore commas inside functions
 884                 my @arr = map{ s/<$escToken>/\,/go; $_ } split( /,\s*/, $theAttr, $theLevel );
 885                 return @arr;
 886             }
 887             
 888             # =========================
 889             sub _escapeCommas
 890             {
 891                 my( $theText ) = @_;
 892                 $theText =~ s/\,/<$escToken>/go;
 893                 return $theText;
 894             }
 895             
 896             # =========================
 897             sub _getNumber
 898             {
 899                 my( $theText ) = @_;
 900                 return 0 unless( $theText );
 901                 $theText =~ s/([0-9])\,(?=[0-9]{3})/$1/go;          # "1,234,567" ==> "1234567"
 902                 unless( $theText =~ s/^.*?(\-?[0-9\.]+).*$/$1/o ) { # "xy-1.23zz" ==> "-1.23"
 903                     $theText = 0;
 904 rizwank 1.1     }
 905                 $theText =~ s/^(\-?)0+([0-9])/$1$2/o;               # "-0009.12"  ==> "-9.12"
 906                 $theText =~ s/^(\-?)\./${1}0\./o;                   # "-.25"      ==> "-0.25"
 907                 $theText =~ s/^\-0$/0/o;                            # "-0"        ==> "0"
 908                 return $theText;
 909             }
 910             
 911             # =========================
 912             sub safeEvalPerl
 913             {
 914                 my( $theText ) = @_;
 915             
 916                 # Allow only simple math with operators - + * / % ( )
 917                 $theText =~ s/\%\s*[^\-\+\*\/0-9\.\(\)]+//go; # defuse %hash but keep modulus
 918                 # keep only numbers and operators (shh... don't tell anyone, we support comparison operators)
 919                 $theText =~ s/[^\!\<\=\>\-\+\*\/\%0-9\.\(\)]*//go;
 920                 $theText =~ /(.*)/;
 921                 $theText = $1;  # untainted variable
 922                 return "" unless( $theText );
 923                 local $SIG{__DIE__} = sub { TWiki::Func::writeDebug($_[0]); warn $_[0] };
 924                 my $result = eval $theText;
 925 rizwank 1.1     if( $@ ) {
 926                     $result = $@;
 927                     $result =~ s/[\n\r]//go;
 928                     $result =~ s/\[[^\]]+.*view.*?\:\s?//o;                   # Cut "[Mon Mar 15 23:31:39 2004] view: "
 929                     $result =~ s/\s?at \(eval.*?\)\sline\s?[0-9]*\.?\s?//go;  # Cut "at (eval 51) line 2."
 930                     $result = "ERROR: $result";
 931             
 932                 } else {
 933                     $result = 0 unless( $result );  # logical false is "0"
 934                 }
 935                 return $result;
 936             }
 937             
 938             # =========================
 939             sub getListAsInteger
 940             {
 941                 my( $theAttr ) = @_;
 942             
 943                 my $val = 0;
 944                 my @list = getList( $theAttr );
 945                 (my $baz = "foo") =~ s/foo//;  # reset search vars. defensive coding
 946 rizwank 1.1     for my $i (0 .. $#list ) {
 947                     $val = $list[$i];
 948                     # search first integer pattern, skip over HTML tags
 949                     if( $val =~ /^\s*(?:<[^>]*>)*([\-\+]*[0-9]+).*/o ) {
 950                         $list[$i] = $1;  # untainted variable, possibly undef
 951                     } else {
 952                         $list[$i] = undef;
 953                     }
 954                 }
 955                 return @list;
 956             }
 957             
 958             # =========================
 959             sub getListAsFloat
 960             {
 961                 my( $theAttr ) = @_;
 962             
 963                 my $val = 0;
 964                 my @list = getList( $theAttr );
 965                 (my $baz = "foo") =~ s/foo//;  # reset search vars. defensive coding
 966                 for my $i (0 .. $#list ) {
 967 rizwank 1.1         $val = $list[$i] || "";
 968                     # search first float pattern, skip over HTML tags
 969                     if( $val =~ /^\s*(?:<[^>]*>)*\$?([\-\+]*[0-9\.]+).*/o ) {
 970                         $list[$i] = $1;  # untainted variable, possibly undef
 971                     } else {
 972                         $list[$i] = undef;
 973                     }
 974                 }
 975                 return @list;
 976             }
 977             
 978             # =========================
 979             sub getListAsDays
 980             {
 981                 my( $theAttr ) = @_;
 982             
 983                 # contributed by by SvenDowideit - 07 Mar 2003; modified by PTh
 984                 my $val = 0;
 985                 my @arr = getList( $theAttr );
 986                 (my $baz = "foo") =~ s/foo//;  # reset search vars. defensive coding
 987                 for my $i (0 .. $#arr ) {
 988 rizwank 1.1         $val = $arr[$i] || "";
 989                     # search first float pattern
 990                     if( $val =~ /^\s*([\-\+]*[0-9\.]+)\s*d/oi ) {
 991                         $arr[$i] = $1;      # untainted variable, possibly undef
 992                     } elsif( $val =~ /^\s*([\-\+]*[0-9\.]+)\s*w/oi ) {
 993                         $arr[$i] = 5 * $1;  # untainted variable, possibly undef
 994                     } elsif( $val =~ /^\s*([\-\+]*[0-9\.]+)\s*h/oi ) {
 995                         $arr[$i] = $1 / 8;  # untainted variable, possibly undef
 996                     } elsif( $val =~ /^\s*([\-\+]*[0-9\.]+)/o ) {
 997                         $arr[$i] = $1;      # untainted variable, possibly undef
 998                     } else {
 999                         $arr[$i] = undef;
1000                     }
1001                 }
1002                 return @arr;
1003             }
1004             
1005             # =========================
1006             sub getList
1007             {
1008                 my( $theAttr ) = @_;
1009 rizwank 1.1 
1010                 my @list = ();
1011                 foreach( split( /,\s*/, $theAttr ) ) {
1012                     if( m/\s*R([0-9]+)\:C([0-9]+)\s*\.\.+\s*R([0-9]+)\:C([0-9]+)/ ) {
1013                         # table range
1014                         push( @list, getTableRange( $_ ) );
1015                     } else {
1016                         # list item
1017                         $list[$#list+1] = $_;
1018                     }
1019                 }
1020                 return @list;
1021             }
1022             
1023             # =========================
1024             sub getTableRange
1025             {
1026                 my( $theAttr ) = @_;
1027             
1028                 my @arr = ();
1029                 if( $rPos < 0 ) {
1030 rizwank 1.1         return @arr;
1031                 }
1032             
1033                 &TWiki::Func::writeDebug( "- SpreadSheetPlugin::getTableRange( $theAttr )" ) if $debug;
1034                 unless( $theAttr =~ /\s*R([0-9]+)\:C([0-9]+)\s*\.\.+\s*R([0-9]+)\:C([0-9]+)/ ) {
1035                     return @arr;
1036                 }
1037                 my $r1 = $1 - 1;
1038                 my $c1 = $2 - 1;
1039                 my $r2 = $3 - 1;
1040                 my $c2 = $4 - 1;
1041                 my $r = 0;
1042                 my $c = 0;
1043                 if( $c1 < 0     ) { $c1 = 0; }
1044                 if( $c2 < 0     ) { $c2 = 0; }
1045                 if( $c2 < $c1   ) { $c = $c1; $c1 = $c2; $c2 = $c; }
1046                 if( $r1 > $rPos ) { $r1 = $rPos; }
1047                 if( $r1 < 0     ) { $r1 = 0; }
1048                 if( $r2 > $rPos ) { $r2 = $rPos; }
1049                 if( $r2 < 0     ) { $r2 = 0; }
1050                 if( $r2 < $r1   ) { $r = $r1; $r1 = $r2; $r2 = $r; }
1051 rizwank 1.1 
1052                 my $pRow = ();
1053                 for $r ( $r1 .. $r2 ) {
1054                     $pRow = $tableMatrix[$r];
1055                     for $c ( $c1 .. $c2 ) {
1056                         if( $c < @$pRow ) {
1057                             push( @arr, $$pRow[$c] );
1058                         }
1059                     }
1060                 }
1061                 &TWiki::Func::writeDebug( "- SpreadSheetPlugin::getTableRange() returns @arr" ) if $debug;
1062                 return @arr;
1063             }
1064             
1065             # =========================
1066             sub _date2serial
1067             {
1068                 my ( $theText ) = @_;
1069             
1070                 my $sec = 0; my $min = 0; my $hour = 0; my $day = 1; my $mon = 0; my $year = 0;
1071             
1072 rizwank 1.1     if( $theText =~ m|([0-9]{1,2})[-\s/]+([A-Z][a-z][a-z])[-\s/]+([0-9]{4})[-\s/]+([0-9]{1,2}):([0-9]{1,2})| ) {
1073                     # "31 Dec 2003 - 23:59", "31-Dec-2003 - 23:59", "31 Dec 2003 - 23:59 - any suffix"
1074                     $day = $1; $mon = $mon2num{$2} || 0; $year = $3 - 1900; $hour = $4; $min = $5;
1075                 } elsif( $theText =~ m|([0-9]{1,2})[-\s/]+([A-Z][a-z][a-z])[-\s/]+([0-9]{2,4})| ) {
1076                     # "31 Dec 2003", "31 Dec 03", "31-Dec-2003", "31/Dec/2003"
1077                     $day = $1; $mon = $mon2num{$2} || 0; $year = $3;
1078                     $year += 100 if( $year < 80 );      # "05"   --> "105" (leave "99" as is)
1079                     $year -= 1900 if( $year >= 1900 );  # "2005" --> "105"
1080                 } elsif( $theText =~ m|([0-9]{4})[-/\.]([0-9]{1,2})[-/\.]([0-9]{1,2})[-/\.\,\s]+([0-9]{1,2})[-\:/\.]([0-9]{1,2})[-\:/\.]([0-9]{1,2})| ) {
1081                     # "2003/12/31 23:59:59", "2003-12-31-23-59-59", "2003.12.31.23.59.59"
1082                     $year = $1 - 1900; $mon = $2 - 1; $day = $3; $hour = $4; $min = $5; $sec = $6;
1083                 } elsif( $theText =~ m|([0-9]{4})[-/\.]([0-9]{1,2})[-/\.]([0-9]{1,2})[-/\.\,\s]+([0-9]{1,2})[-\:/\.]([0-9]{1,2})| ) {
1084                     # "2003/12/31 23:59", "2003-12-31-23-59", "2003.12.31.23.59"
1085                     $year = $1 - 1900; $mon = $2 - 1; $day = $3; $hour = $4; $min = $5;
1086                 } elsif( $theText =~ m|([0-9]{4})[-/]([0-9]{1,2})[-/]([0-9]{1,2})| ) {
1087                     # "2003/12/31", "2003-12-31"
1088                     $year = $1 - 1900; $mon = $2 - 1; $day = $3;
1089                 } elsif( $theText =~ m|([0-9]{1,2})[-/]([0-9]{1,2})[-/]([0-9]{2,4})| ) {
1090                     # "12/31/2003", "12/31/03", "12-31-2003"
1091                     # (shh, don't tell anyone that we support ambiguous American dates, my boss asked me to)
1092                     $year = $3; $mon = $1 - 1; $day = $2;
1093 rizwank 1.1         $year += 100 if( $year < 80 );      # "05"   --> "105" (leave "99" as is)
1094                     $year -= 1900 if( $year >= 1900 );  # "2005" --> "105"
1095                 } else {
1096                     # unsupported format
1097                     return 0;
1098                 }
1099                 if( ( $sec > 60 ) || ( $min > 59 ) || ( $hour > 23 ) || ( $day < 1 ) || ( $day > 31 ) || ( $mon > 11 ) ) {
1100                     # unsupported, out of range
1101                     return 0;
1102                 }
1103                 if( $theText =~ /gmt/i ) {
1104                     return timegm( $sec, $min, $hour, $day, $mon, $year );
1105                 } else {
1106                     return timelocal( $sec, $min, $hour, $day, $mon, $year );
1107                 }
1108             }
1109             
1110             # =========================
1111             sub _serial2date
1112             {
1113                 my ( $theTime, $theStr, $isGmt ) = @_;
1114 rizwank 1.1 
1115                 my( $sec, $min, $hour, $day, $mon, $year, $wday, $yday ) = localtime( $theTime );
1116                 (   $sec, $min, $hour, $day, $mon, $year, $wday, $yday ) = gmtime( $theTime ) if( $isGmt );
1117             
1118                 $theStr =~ s/\$sec[o]?[n]?[d]?[s]?/sprintf("%.2u",$sec)/geoi;
1119                 $theStr =~ s/\$min[u]?[t]?[e]?[s]?/sprintf("%.2u",$min)/geoi;
1120                 $theStr =~ s/\$hou[r]?[s]?/sprintf("%.2u",$hour)/geoi;
1121                 $theStr =~ s/\$day/sprintf("%.2u",$day)/geoi;
1122                 $theStr =~ s/\$mon(?!t)/$monArr[$mon]/goi;
1123                 $theStr =~ s/\$mo[n]?[t]?[h]?/sprintf("%.2u",$mon+1)/geoi;
1124                 $theStr =~ s/\$yearday/$yday+1/geoi;
1125                 $theStr =~ s/\$yea[r]?/sprintf("%.4u",$year+1900)/geoi;
1126                 $theStr =~ s/\$ye/sprintf("%.2u",$year%100)/geoi;
1127                 $theStr =~ s/\$wday/substr($wdayArr[$wday],0,3)/geoi;
1128                 $theStr =~ s/\$wd/$wday+1/geoi;
1129                 $theStr =~ s/\$weekday/$wdayArr[$wday]/goi;
1130             
1131                 return $theStr;
1132             }
1133             
1134             # =========================
1135 rizwank 1.1 sub _properSpace
1136             {
1137                 my ( $theStr ) = @_;
1138             
1139                 # FIXME: I18N
1140             
1141                 unless( $dontSpaceRE ) {
1142                     $dontSpaceRE = &TWiki::Func::getPreferencesValue( "DONTSPACE" ) ||
1143                                    &TWiki::Func::getPreferencesValue( "SPREADSHEETPLUGIN_DONTSPACE" ) ||
1144                                    "UnlikelyGibberishWikiWord";
1145                     $dontSpaceRE =~ s/[^a-zA-Z0-9\,\s]//go;
1146                     $dontSpaceRE = "(" . join( "|", split( /[\,\s]+/, $dontSpaceRE ) ) . ")";
1147                     # Example: "(RedHat|McIntosh)"
1148                 }
1149                 $theStr =~ s/$dontSpaceRE/_spaceWikiWord( $1, "<DONT_SPACE>" )/geo;  # e.g. "Mc<DONT_SPACE>Intosh"
1150                 $theStr =~ s/(^|[\s\(]|\]\[)([a-zA-Z0-9]+)/$1 . _spaceWikiWord( $2, " " )/geo;
1151                 $theStr =~ s/<DONT_SPACE>//go;  # remove "<DONT_SPACE>" marker
1152             
1153                 return $theStr;
1154             }
1155             
1156 rizwank 1.1 # =========================
1157             sub _spaceWikiWord
1158             {
1159                 my ( $theStr, $theSpacer ) = @_;
1160             
1161                 $theStr =~ s/([a-z])([A-Z0-9])/$1$theSpacer$2/go;
1162                 $theStr =~ s/([0-9])([a-zA-Z])/$1$theSpacer$2/go;
1163             
1164                 return $theStr;
1165             }
1166             
1167             # =========================
1168             sub _workingDays
1169             {
1170                 my ( $start, $end ) = @_;
1171             
1172                 # Contributed by CrawfordCurrie - 17 Jul 2004
1173                 # Calculate working days between two times. Times are standard system times (secs since 1970). 
1174                 # Working days are Monday through Friday (sorry, Israel!)
1175             
1176                 use integer;
1177 rizwank 1.1     my $elapsed_days = ( $end - $start ) / ( 60 * 60 * 24 );
1178                 # total number of elapsed 7-day weeks
1179                 my $whole_weeks = $elapsed_days / 7;
1180                 my $extra_days = $elapsed_days - ( $whole_weeks * 7 );
1181                 if( $extra_days > 0 ) {
1182                   my @lt = gmtime( $start );
1183                   my $wday = $lt[6]; # weekday, 0 is sunday
1184             
1185                   if( $wday == 0 ) {
1186                     $extra_days-- if( $extra_days > 0 );
1187                   } else {
1188                     $extra_days-- if( $extra_days > ( 6 - $wday ) );
1189                     $extra_days-- if( $extra_days > ( 6 - $wday ) );
1190                   }
1191                 }
1192                 return $whole_weeks * 5 + $extra_days;
1193             }
1194             
1195             # =========================
1196             
1197             1;
1198 rizwank 1.1 
1199             # EOF

Rizwan Kassim
Powered by
ViewCVS 0.9.2