# # TWiki WikiClone ($wikiversion has version info) # # Copyright (C) 2001 Peter Thoeny, Peter@Thoeny.com # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details, published at # http://www.gnu.org/copyleft/gpl.html # # ========================= # # This is the spreadsheet TWiki plugin. # # Each plugin is a package that contains the subs: # # initPlugin ( $topic, $web, $user ) # commonTagsHandler ( $text, $topic, $web ) # startRenderingHandler( $text, $web ) # outsidePREHandler ( $text ) # insidePREHandler ( $text ) # endRenderingHandler ( $text ) # # initPlugin is required, all other are optional. # For increased performance, DISABLE handlers you don't need. # ========================= package TWiki::Plugins::SpreadSheetPlugin; # ========================= use vars qw( $web $topic $user $installWeb $VERSION $debug $skipInclude $renderingWeb @tableMatrix %namedMatrix $cPos $rPos $escToken ); $VERSION = '1.001'; $escToken = "\263"; # ========================= sub initPlugin { ( $topic, $web, $user, $installWeb ) = @_; # check for Plugins.pm versions if( $TWiki::Plugins::VERSION < 1 ) { &TWiki::Func::writeWarning( "Version mismatch between SpreadSheetPlugin and Plugins.pm" ); return 0; } $renderingWeb = $web; # Get plugin debug flag $debug = &TWiki::Func::getPreferencesFlag( "SPREADSHEETPLUGIN_DEBUG" ); # Get plugin debug flag $skipInclude = &TWiki::Func::getPreferencesFlag( "SPREADSHEETPLUGIN_SKIPINCLUDE" ); # Plugin correctly initialized &TWiki::Func::writeDebug( "- TWiki::Plugins::SpreadSheetPlugin::initPlugin( $web.$topic ) is OK" ) if $debug; return 1; } # ========================= sub commonTagsHandler { ### my ( $text, $topic, $web ) = @_; # do not uncomment, use $_[0], $_[1]... instead &TWiki::Func::writeDebug( "- SpreadSheetPlugin::commonTagsHandler( $_[2].$_[1] )" ) if $debug; if( ( $_[3] ) && ( $skipInclude ) ) { # bail out, handler called from an %INCLUDE{}% return; } unless( $_[0] =~ /%CALC\{.*?\}%/ || $_[0] =~ /%CALCRANGE\{.*?\}%/ ) { # nothing to do return; } %namedMatrix = (); @tableMatrix = (); $cPos = -1; $rPos = -1; my $result = ""; my $insidePRE = 0; my $insideTABLE = 0; my $line = ""; my $before = ""; my $cell = ""; my @row = (); my $tableName = ""; $_[0] =~ s/\r//go; $_[0] =~ s/\\\n//go; # Join lines ending in "\" foreach( split( /\n/, $_[0] ) ) { # change state: m|
|i && ( $insidePRE = 1 );
m||i && ( $insidePRE = 1 );
m| |i && ( $insidePRE = 0 );
m||i && ( $insidePRE = 0 );
if( ! ( $insidePRE ) ) {
if( /^\s*\|.*\|\s*$/ ) {
# inside | table |
if( ! $insideTABLE ) {
$insideTABLE = 1;
@tableMatrix = (); # reset table matrix
$cPos = -1;
$rPos = -1;
}
$line = $_;
$line =~ s/^(\s*\|)(.*)\|\s*$/$2/o;
$before = $1;
@row = split( /\|/o, $line, -1 );
push @tableMatrix, [ @row ];
$rPos++;
$line = "$before";
for( $cPos = 0; $cPos < @row; $cPos++ ) {
$cell = $row[$cPos];
$cell =~ s/%CALC\{(.*?)\}%/&doCalc($1)/geo;
$cell =~ s/%CALCRANGE\{(.*?)\}%/&doRangeDef($1)/geo;
$line .= "$cell|";
}
s/.*/$line/o;
$result .= "$_\n";
} elsif ( /^\s*\#\|.*\|\s*$/ ) {
# inside hidden table row ( #| fdsfdsf | )
if( ! $insideTABLE ) {
$insideTABLE = 1;
@tableMatrix = ();
$cPos = -1;
$rPos = -1;
}
$line = $_;
$line =~ s/^\s*\#\|(.*)\|\s*$/$1/o;
@row = split( /\|/o, $line, -1 );
push @tableMatrix, [ @row ];
$rPos++;
for( $cPos = 0; $cPos < @row; $cPos++ ) {
$cell = $row[$cPos];
$cell =~ s/%CALC\{(.*?)\}%/&doCalc($1)/geo;
$cell =~ s/%CALCRANGE\{(.*?)\}%/&doRangeDef($1)/geo;
}
# don't generate an output line in this case
} else {
# outside | table |
if( $insideTABLE ) {
$insideTABLE = 0;
}
s/%CALC\{(.*?)\}%/&doCalc($1)/geo;
s/%CALCRANGE\{(.*?)\}%/&doRangeDef($1)/geo;
$result .= "$_\n";
}
}
else {
$result .= "$_\n";
}
}
$_[0] = $result;
}
# =========================
sub doCalc
{
my( $theAttributes ) = @_;
my $text = &TWiki::extractNameValuePair( $theAttributes );
# Add nesting level to parenthesis,
# e.g. "A(B())" gets "A-esc-1(B-esc-2(-esc-2)-esc-1)"
$text =~ s/([\(\)])/addNestingLevel($1, \$level)/geo;
$text = doFunc( "MAIN", $text );
if( ( $rPos >= 0 ) && ( $cPos >= 0 ) ) {
# update cell in table matrix
$tableMatrix[$rPos][$cPos] = $text;
}
return $text;
}
# =========================
sub doRangeDef
{
my( $theAttributes ) = @_;
my $name = &TWiki::extractNameValuePair( $theAttributes, "name" );
my $text = &TWiki::extractNameValuePair( $theAttributes, "range" );
&TWiki::writeDebug( "- SpreadSheetPlugin::doRangeDef: @_ : $theAttributes ($name = $text)" ) if $debug;
# Add nesting level to parenthesis,
# e.g. "A(B())" gets "A-esc-1(B-esc-2(-esc-2)-esc-1)"
$text =~ s/([\(\)])/addNestingLevel($1, \$level)/geo;
$text = doFunc( "MAIN", $text );
$namedMatrix{$name} = getTableRangeMatrix($text);
&TWiki::writeDebug( "- SpreadSheetPlugin::doRangeDef: namedMatrix \{ $name \} = $namedMatrix{$name} -> $$namedMatrix{$name} " ) if $debug;
return ""; # result is always empty string
}
# =========================
sub addNestingLevel
{
my( $theParen, $theLevelRef ) = @_;
my $result = "";
if( $theParen eq "(" ) {
$$theLevelRef++;
$result = "$escToken$$theLevelRef$theParen";
} else {
$result = "$escToken$$theLevelRef$theParen";
$$theLevelRef--;
}
return $result;
}
# =========================
sub doFunc
{
my( $theFunc, $theAttr ) = @_;
&TWiki::writeDebug( "- SpreadSheetPlugin::doFunc: $theFunc( $theAttr ) start" ) if $debug;
# Handle functions recursively
$theAttr =~ s/\$([A-Z]+)$escToken([0-9]+)\((.*?)$escToken\2\)/&doFunc($1,$3)/geo;
# Clean up unbalanced mess
$theAttr =~ s/$escToken\-*[0-9]+([\(\)])/$1/go;
my $result = "";
my $i = 0;
if( $theFunc eq "MAIN" ) {
$result = $theAttr;
} elsif( $theFunc eq "T" ) {
$result = "";
my @arr = getTableCell( $theAttr );
if( @arr ) {
$result = $arr[0];
}
} elsif( $theFunc eq "TRIM" ) {
$result = $theAttr || "";
$result =~ s/^\s*//o;
$result =~ s/\s*$//o;
$result =~ s/\s+/ /go;
} elsif( $theFunc eq "FORMAT" ) {
# Format FORMAT(TYPE, precision, value) returns formatted value -- JimStraus - 05 Jan 2003
my( $format, $res, $value ) = split( /,\s*/, $theAttr );
$format =~ s/^\s*(.*?)\s*$/$1/; #Strip leading and trailing spaces
$res =~ s/^\s*(.*?)\s*$/$1/;
$value =~ s/^\s*(.*?)\s*$/$1/;
if ($format eq "DOLLAR") {
my $neg = 1 if $value < 0;
$value = abs($value);
$result = sprintf("%0.${res}f", $value);
my $temp = reverse $result;
$temp =~ s/(\d\d\d)(?=\d)(?!\d*\.)/$1,/g;
$result = "\$" . (scalar reverse $temp);
$result = "(".$result.")" if $neg;
} elsif ($format eq "COMMA") {
$result = sprintf("%0.${res}f", $value);
my $temp = reverse $result;
$temp =~ s/(\d\d\d)(?=\d)(?!\d*\.)/$1,/g;
$result = scalar reverse $temp;
} elsif ($format eq "PERCENT") {
$result = sprintf("%0.${res}f\%", $value * 100);
} elsif ($format eq "NUMBER") {
$result = sprintf("%0.${res}f", $value);
} else {
#FORMAT not recognized, just return value
$result = $value;
}
} elsif( $theFunc eq "EXACT" ) {
$result = 0;
my( $str1, $str2 ) = split( /,\s*/, $theAttr, 2 );
$str1 = "" unless( $str1 );
$str2 = "" unless( $str2 );
$str1 =~ s/^\s*(.*?)\s*$/$1/o; # cut leading and trailing spaces
$str2 =~ s/^\s*(.*?)\s*$/$1/o;
$result = 1 if( $str1 eq $str2 );
} elsif( $theFunc =~ /^(EVAL|INT|ROUND)$/ ) {
$result = safeEvalPerl( $theFunc, $theAttr );
} elsif( $theFunc eq "IF" ) {
# IF(condition, value if true, value if false)
my( $condition, $str1, $str2 ) = split( /,\s*/, $theAttr, 3 );
$condition =~ s/^\s*(.*?)\s*$/$1/o;
$result = safeEvalPerl( "EVAL", $condition );
if( $result =~ /^ERROR/ ) {
# return error message
} elsif( $result ) {
$result = $str1 || "";
} else {
$result = $str2 || "";
}
} elsif( $theFunc eq "UPPER" ) {
$result = uc( $theAttr );
} elsif( $theFunc eq "LOWER" ) {
$result = lc( $theAttr );
} elsif( $theFunc eq "CHAR" ) {
$theAttr =~ /([0-9]+)/;
$i = $1 || 0;
$i = 255 if $i > 255;
$i = 0 if $i < 0;
$result = chr( $i );
} elsif( $theFunc eq "CODE" ) {
$result = ord( $theAttr );
} elsif( $theFunc eq "LENGTH" ) {
$result = length( $theAttr );
} elsif( $theFunc eq "ROW" ) {
$i = $theAttr || 0;
$result = $rPos + $i + 1;
} elsif( $theFunc eq "COLUMN" ) {
$i = $theAttr || 0;
$result = $cPos + $i + 1;
} elsif( $theFunc eq "LEFT" ) {
$i = $rPos + 1;
$result = "R$i:C0..R$i:C$cPos";
} elsif( $theFunc eq "ABOVE" ) {
$i = $cPos + 1;
$result = "R0:C$i..R$rPos:C$i";
} elsif( $theFunc eq "RIGHT" ) {
$i = $rPos + 1;
$result = "R$i:C$cPos..R$i:C32000";
} elsif( $theFunc eq "DEF" ) {
# Format DEF(list) returns first defined cell
# Added by MF 26/3/2002, fixed by PeterThoeny
my @arr = getTableRange( $theAttr );
foreach my $cell ( @arr ) {
if( $cell ) {
$cell =~ s/^\s*(.*?)\s*$/$1/o;
if( $cell ) {
$result = $cell;
last;
}
}
}
} elsif( $theFunc eq "MAX" ) {
my @arr = sort { $a <=> $b }
grep { /./ }
grep { defined $_ }
getTableRangeAsFloat( $theAttr );
$result = $arr[$#arr];
} elsif( $theFunc eq "MIN" ) {
my @arr = sort { $a <=> $b }
grep { /./ }
grep { defined $_ }
getTableRangeAsFloat( $theAttr );
$result = $arr[0];
} elsif( $theFunc eq "SUM" ) {
$result = 0;
my @arr = getTableRangeAsFloat( $theAttr );
foreach $i ( @arr ) {
$result += $i if defined $i;
}
} elsif( $theFunc eq "AVERAGE" ) {
$result = 0;
my $items = 0;
my @arr = getTableRangeAsFloat( $theAttr );
foreach $i ( @arr ) {
if( defined $i ) {
$result += $i;
$items++;
}
}
if( $items > 0 ) {
$result = $result / $items;
}
} elsif( $theFunc eq "COUNTSTR" ) {
$result = 0; # count any string
$i = 0; # count string equal second attr
my( $range, $str ) = split( /,\s*/, $theAttr, 2 );
$str =~ s/\s*$//o if( $str );
my @arr = getTableRange( $range );
foreach my $cell ( @arr ) {
if( defined $cell ) {
$cell =~ s/^\s*(.*?)\s*$/$1/o;
$result++ if( $cell );
$i++ if( $str && ( $cell eq $str ) );
}
}
$result = $i if( $str );
} elsif( $theFunc eq "COUNTITEMS" ) {
$result = "";
my @arr = getTableRange( $theAttr );
my %items = ();
my $key = "";
foreach $key ( @arr ) {
$key =~ s/^\s*(.*?)\s*$/$1/o if( $key );
if( $key ) {
if( exists( $items{ $key } ) ) {
$items{ $key }++;
} else {
$items{ $key } = 1;
}
}
}
foreach $key ( sort keys %items ) {
$result .= "$key: $items{ $key } tag
# This is the place to define customized rendering rules
# do custom extension rule, like for example:
# $_[0] =~ s/old/new/go;
}
# =========================
sub DISABLE_insidePREHandler
{
### my ( $text ) = @_; # do not uncomment, use $_[0] instead
&TWiki::Func::writeDebug( "- SpreadSheetPlugin::insidePREHandler( $web.$topic )" ) if $debug;
# This handler is called by getRenderedVersion, in loop inside of tag
# This is the place to define customized rendering rules
# do custom extension rule, like for example:
# $_[0] =~ s/old/new/go;
}
# =========================
sub DISABLE_endRenderingHandler
{
### my ( $text ) = @_; # do not uncomment, use $_[0] instead
&TWiki::Func::writeDebug( "- SpreadSheetPlugin::endRenderingHandler( $_[0] )" ) if $debug;
# This handler is called by getRenderedVersion just after the line loop
}
# =========================
1;
# EOF