lib/TWiki/Plugins/EditTablePlugin/Core.pm
author Colas Nahaboo <colas@nahaboo.net>
Sat, 26 Jan 2008 15:50:53 +0100
changeset 0 414e01d06fd5
child 1 e2915a7cbdfa
permissions -rw-r--r--
RELEASE 4.2.0 freetown
     1 # Plugin for TWiki Enterprise Collaboration Platform, http://TWiki.org/
     2 #
     3 # Copyright (C) 2002-2007 Peter Thoeny, peter@thoeny.org and
     4 # TWiki Contributors.
     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. For
    10 # more details read LICENSE in the root of this distribution.
    11 #
    12 # This program is distributed in the hope that it will be useful,
    13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
    14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    15 #
    16 # As per the GPL, removal of this notice is prohibited.
    17 #
    18 # This is the EditTablePlugin used to edit tables in place.
    19 
    20 package TWiki::Plugins::EditTablePlugin::Core;
    21 
    22 use strict;
    23 use Assert;
    24 
    25 use vars qw(
    26   $preSp %params @format @formatExpanded
    27   $prefsInitialized $prefCHANGEROWS $prefEDIT_BUTTON $prefSAVE_BUTTON $prefQUIET_SAVE_BUTTON $prefADD_ROW_BUTTON $prefDELETE_LAST_ROW_BUTTON $prefCANCEL_BUTTON $prefMESSAGE_INCLUDED_TOPIC_DOES_NOT_EXIST
    28   $prefQUIETSAVE
    29   $nrCols $encodeStart $encodeEnd $table $query %regex
    30   $warningMessage
    31 );
    32 
    33 my $RENDER_HACK        = "\n<nop>\n";
    34 my $DEFAULT_FIELD_SIZE = 16;
    35 
    36 BEGIN {
    37     %regex                    = ();
    38     $regex{edit_table_plugin} = '%EDITTABLE{(.*?)}%';
    39     $regex{table_plugin}      = '%TABLE(?:{(.*?)})?%';
    40     $regex{table_row_full}    = '^(\s*)\|.*\|\s*$';
    41     $regex{table_row}         = '^(\s*)\|(.*)';
    42 }
    43 
    44 sub init {
    45     $preSp                      = '';
    46     %params                     = ();
    47     @format                     = ();
    48     @formatExpanded             = ();
    49     $prefsInitialized           = undef;
    50     $prefCHANGEROWS             = undef;
    51     $prefEDIT_BUTTON            = undef;
    52     $prefSAVE_BUTTON            = undef;
    53     $prefQUIET_SAVE_BUTTON      = undef;
    54     $prefADD_ROW_BUTTON         = undef;
    55     $prefDELETE_LAST_ROW_BUTTON = undef;
    56     $prefDELETE_LAST_ROW_BUTTON = undef;
    57     $prefQUIETSAVE              = undef;
    58     $nrCols                     = undef;
    59     $encodeStart                = undef;
    60     $encodeEnd                  = undef;
    61     $table                      = undef;
    62     $query                      = undef;
    63     $warningMessage             = '';
    64 }
    65 
    66 =pod
    67 
    68 ---+++ process( $doSave, $saveTableNr, $doSaveQuiet, $text, $topic, $web )
    69 
    70 Called from commonTagsHandler. Pass over to processText in 'no Save' mode.
    71 
    72 =cut
    73 
    74 sub process {
    75     init();
    76     my $saveMode      = $TWiki::Plugins::EditTablePlugin::saveMode{'NONE'};
    77     my $saveTableNr   = 0;
    78     my $saveQuietMode = $TWiki::Plugins::EditTablePlugin::saveMode{'SAVEQUIET'};
    79     processText( $saveMode, $saveTableNr, $saveQuietMode, @_ );
    80 }
    81 
    82 =pod
    83 
    84 ---+++ processText( $doSave, $saveTableNr, $doSaveQuiet, $text, $topic, $web )
    85 
    86 Process the text line by line.
    87 When a EditTablePlugin table is encountered, its contents is rendered according to the view: 
    88    * View mode - default
    89    * Edit mode - when an Edit button is clicked, renders the rest of the table in edit mode
    90    * Save mode - when called from a Save button: calls processText again, only renders the selected table number, then saves the topic text
    91 
    92 =cut
    93 
    94 sub processText {
    95 
    96     my $doSave = ( shift == $TWiki::Plugins::EditTablePlugin::saveMode{'SAVE'} )
    97       || 0;
    98     my $saveTableNr = shift;
    99     my $doSaveQuiet =
   100       ( shift == $TWiki::Plugins::EditTablePlugin::saveMode{'SAVEQUIET'} ) || 0;
   101 
   102     $query = TWiki::Func::getCgiQuery();
   103 
   104     TWiki::Func::writeDebug(
   105         "- EditTablePlugin::commonTagsHandler( $_[2].$_[1] )")
   106       if $TWiki::Plugins::EditTablePlugin::debug;
   107 
   108     unless ($prefsInitialized) {
   109         $prefCHANGEROWS = TWiki::Func::getPreferencesValue('CHANGEROWS')
   110           || TWiki::Func::getPreferencesValue('EDITTABLEPLUGIN_CHANGEROWS')
   111           || 'on';
   112         $prefQUIETSAVE = TWiki::Func::getPreferencesValue('QUIETSAVE')
   113           || TWiki::Func::getPreferencesValue('EDITTABLEPLUGIN_QUIETSAVE')
   114           || 'on';
   115         $prefEDIT_BUTTON = TWiki::Func::getPreferencesValue('EDIT_BUTTON')
   116           || TWiki::Func::getPreferencesValue('EDITTABLEPLUGIN_EDIT_BUTTON')
   117           || 'Edit table';
   118         $prefSAVE_BUTTON = TWiki::Func::getPreferencesValue('SAVE_BUTTON')
   119           || TWiki::Func::getPreferencesValue('EDITTABLEPLUGIN_SAVE_BUTTON')
   120           || 'Save table';
   121         $prefQUIET_SAVE_BUTTON =
   122           TWiki::Func::getPreferencesValue('QUIET_SAVE_BUTTON')
   123           || TWiki::Func::getPreferencesValue(
   124             'EDITTABLEPLUGIN_QUIET_SAVE_BUTTON')
   125           || 'Quiet save';
   126         $prefADD_ROW_BUTTON = TWiki::Func::getPreferencesValue('ADD_ROW_BUTTON')
   127           || TWiki::Func::getPreferencesValue('EDITTABLEPLUGIN_ADD_ROW_BUTTON')
   128           || 'Add row';
   129         $prefDELETE_LAST_ROW_BUTTON =
   130           TWiki::Func::getPreferencesValue('DELETE_LAST_ROW_BUTTON')
   131           || TWiki::Func::getPreferencesValue(
   132             'EDITTABLEPLUGIN_DELETE_LAST_ROW_BUTTON')
   133           || 'Delete last row';
   134         $prefCANCEL_BUTTON = TWiki::Func::getPreferencesValue('CANCEL_BUTTON')
   135           || TWiki::Func::getPreferencesValue('EDITTABLEPLUGIN_CANCEL_BUTTON')
   136           || 'Cancel';
   137         $prefMESSAGE_INCLUDED_TOPIC_DOES_NOT_EXIST =
   138           TWiki::Func::getPreferencesValue('INCLUDED_TOPIC_DOES_NOT_EXIST')
   139           || TWiki::Func::getPreferencesValue(
   140             'EDITTABLEPLUGIN_INCLUDED_TOPIC_DOES_NOT_EXIST')
   141           || 'Warning: \'include\' topic does not exist!';
   142 
   143         $prefsInitialized = 1;
   144     }
   145 
   146     my $theTopic = $query->param('ettabletopic') || $_[1];
   147     my $theWeb   = $query->param('ettableweb')   || $_[2];
   148     my $invokedFromTopic = $_[3];    # not used yet
   149     my $invokedFromWeb   = $_[4];    # not used yet
   150 
   151     my $result = '';
   152 
   153     my $insidePRE    = 0;
   154     my $cgiTableNr   = 0;
   155     my $tableNr      = 0;      # current EditTable table
   156     my $isAtTheTable = 0;
   157     my $rowNr        = 0;      # current row number; starting at 1
   158     my $enableForm   = 0;
   159     my $insideTable  = 0;
   160     my $doEdit       = $doSave;
   161     my $hasTableRow  = 0;      # the current line has a row with '| some text |'
   162     my $hasTableTag  = 0;      # the current line has a %TABLE{}% variable tag
   163     my $createdNewTable = 0;
   164     my @rows            = ();
   165     my $etrows          = -1
   166       ; # the number of content rows as passed as form parameter: only available on edit or save; -1 if not rendered
   167     my $etrowsParam;
   168     my $addedRowCount      = 0;
   169     my $addedRowCountParam = 0;
   170     my $headerRowCount     = 0;    #$query->param('etheaderrows') || 0;
   171     my $footerRowCount     = 0;    #$query->param('etfooterrows') || 0;
   172     my $endOfTable         = 0;
   173 
   174     my $theText;
   175     if ($doSave) {
   176         $theText = TWiki::Func::readTopicText( $theWeb, $theTopic );
   177     }
   178     else {
   179         $theText = $_[0];
   180     }
   181 
   182     $theText =~
   183       s/\r//go;    # strip out all \r chars (may be pasted into a table cell)
   184     $theText =~ s/\\\n//go;    # Join lines ending in "\"
   185     $theText .= $RENDER_HACK
   186       ;    # appended stuff is a hack to handle EDITTABLE correctly if at end
   187 
   188     my @lines = split( /\n/, $theText );
   189     for (@lines) {
   190 
   191         # Check if we are inside <pre> or <verbatim> tags
   192         # if so, do not process
   193         m|<pre>|i       && ( $insidePRE = 1 );
   194         m|<verbatim>|i  && ( $insidePRE = 1 );
   195         m|</pre>|i      && ( $insidePRE = 0 );
   196         m|</verbatim>|i && ( $insidePRE = 0 );
   197 
   198         if ($insidePRE) {
   199 
   200             # no need to process, just copy the line
   201             $result .= "$_\n";
   202             next;
   203         }
   204 
   205         my $isLineWithEditTableTag = m/(\s*)$regex{edit_table_plugin}/go;
   206         if ($isLineWithEditTableTag) {
   207 
   208             # this is a line with an EDITTABLE tag
   209             if ($doSave) {
   210 
   211                 # no need to process, just copy the line
   212                 $result .= "$_\n";
   213             }
   214             else {
   215                 my $line = $_;
   216 
   217                 # process the tag contents
   218                 my $restRegex = '([^$]*)';
   219                 my $editTablePluginRE =
   220                   "(.*?)$regex{edit_table_plugin}$restRegex";
   221                 $line =~
   222 s/$editTablePluginRE/&handleEditTableTag( $theWeb, $theTopic, $1, $2 )/geo;
   223 
   224                 # TODO: something strange has happened to the prefix
   225                 # it is no longer used by handleEditTableTag
   226                 # we add it here:
   227                 $result .= $1 if $1;
   228                 $result .= $3 if $3;
   229             }
   230             $tableNr++;
   231 
   232             next if ( $doSave && ( $tableNr != $saveTableNr ) );
   233             $enableForm = 1;
   234 
   235             $cgiTableNr = $query->param('ettablenr')
   236               || 0;    # only on save and edit
   237             $etrowsParam = $query->param('etrows');
   238             $etrows =
   239               ( defined $etrowsParam )
   240               ? $etrowsParam
   241               : -1;
   242             $addedRowCountParam = $query->param('etaddedrows') || 0;
   243             $addedRowCount = $addedRowCountParam;
   244 
   245             $isAtTheTable = 0;
   246             if (
   247                 ( $cgiTableNr == $tableNr )
   248                 && (  $theWeb . '.'
   249                     . $theTopic eq
   250 "$TWiki::Plugins::EditTablePlugin::web.$TWiki::Plugins::EditTablePlugin::topic"
   251                 )
   252               )
   253             {
   254                 $isAtTheTable = 1;
   255                 if ( !$doSave && $query->param('etsave') ) {
   256 
   257                     # [Save table] button pressed
   258                     my $theSaveMode =
   259                       $TWiki::Plugins::EditTablePlugin::saveMode{'SAVE'};
   260                     my $theSaveQuietMode =
   261                       $TWiki::Plugins::EditTablePlugin::saveMode{'NONE'};
   262 
   263                     return processText( $theSaveMode, $tableNr,
   264                         $theSaveQuietMode, @_ );
   265                 }
   266                 elsif ( !$doSave && $query->param('etqsave') ) {
   267 
   268                     # [Quiet save] button pressed
   269                     my $theSaveMode =
   270                       $TWiki::Plugins::EditTablePlugin::saveMode{'SAVE'};
   271                     my $theSaveQuietMode =
   272                       $TWiki::Plugins::EditTablePlugin::saveMode{'SAVEQUIET'};
   273                     return processText( $theSaveMode, $tableNr,
   274                         $theSaveQuietMode, @_ );
   275                 }
   276                 elsif ( $query->param('etcancel') ) {
   277 
   278                     # [Cancel] button pressed
   279                     doCancelEdit( $theWeb, $theTopic );
   280                     ASSERT(0) if DEBUG;
   281                     return;    # in case browser does not redirect
   282                 }
   283                 elsif ( $query->param('etaddrow') ) {
   284 
   285                     # [Add row] button pressed
   286                     $etrows = ( $etrows == -1 ) ? 1 : $etrows + 1;
   287                     $addedRowCount++;
   288                     $doEdit = doEnableEdit( $theWeb, $theTopic, 0 );
   289                     return unless ($doEdit);
   290                 }
   291                 elsif ( $query->param('etdelrow') ) {
   292 
   293                     # [Delete row] button pressed
   294                     if ( $etrows > 0 ) {
   295                         $etrows--;
   296                     }
   297                     $addedRowCount--;
   298                     $doEdit = doEnableEdit( $theWeb, $theTopic, 0 );
   299                     return unless ($doEdit);
   300                 }
   301                 elsif ( $query->param('etedit') ) {
   302 
   303                     # [Edit table] button pressed
   304                     $doEdit = doEnableEdit( $theWeb, $theTopic, 1 );
   305 
   306                     # never return if locked or no permission
   307                     return unless ($doEdit);
   308                 }
   309             }
   310         }    # if $isLineWithEditTableTag
   311 
   312         $hasTableTag = 0;
   313         if (/$regex{table_plugin}/) {
   314 
   315             # match with a TablePlugin line
   316             # works when TABLE tag is just above OR just below the EDITTABLE tag
   317             my %tablePluginParams = TWiki::Func::extractParameters($1);
   318             $headerRowCount = $tablePluginParams{'headerrows'} || 0;
   319             $footerRowCount = $tablePluginParams{'footerrows'} || 0;
   320 
   321             # When editing we append a disableallsort="on" to the TABLE tag
   322             # to prevent TablePlugin from sorting the table. (Item5135)
   323             $_ =~ s/(}%)/ disableallsort="on"$1/ if ( $doEdit && !$doSave );
   324 
   325             $hasTableTag = 1;
   326         }
   327 
   328         $hasTableRow = 0;    # assume no row
   329         if (m/$regex{table_row_full}/) {
   330             $hasTableRow = 1;
   331         }
   332 
   333         if ($enableForm) {
   334 
   335             if ( !$doEdit && !$doSave ) {
   336 
   337                 if ( !$hasTableRow && !$insideTable ) {
   338                     my $tableStart =
   339                       handleTableStart( $theWeb, $theTopic, $tableNr, $doEdit );
   340                     $result .= $tableStart;
   341                     $insideTable = 1;
   342                     $hasTableRow = 1;
   343                     next;
   344                 }
   345                 if ($hasTableRow) {
   346                     $insideTable = 1;
   347                     $rowNr++;
   348                     my $isNewRow = 0;
   349 s/^(\s*)\|(.*)/handleTableRow( $1, $2, $tableNr, $isNewRow, $rowNr, $doEdit, $doSave, $theWeb, $theTopic )/eo;
   350                 }
   351                 elsif ( $insideTable && !$hasTableTag ) {
   352 
   353                     # end of table
   354                     $endOfTable = 1;
   355                     my $rowCount = $rowNr - $headerRowCount - $footerRowCount;
   356                     my $tableEnd = handleTableEnd( $theWeb, $rowCount, $doEdit,
   357                         $headerRowCount, $footerRowCount );
   358                     $result .= $tableEnd;
   359                 }
   360             }    # if !$doEdit && !$doSave
   361 
   362             if ( $doEdit || $doSave ) {
   363                 if ( !$hasTableRow && !$insideTable && !$createdNewTable ) {
   364 
   365                     # start new table
   366                     $createdNewTable = 1;
   367                     if ( !$doSave ) {
   368                         my $tableStart =
   369                           handleTableStart( $theWeb, $theTopic, $tableNr,
   370                             $doEdit );
   371                         $result .= $tableStart;
   372                     }
   373                     $insideTable = 1;
   374                     $hasTableRow = 1;
   375                     next;
   376                 }
   377                 if ($hasTableRow) {
   378                     $insideTable = 1;
   379                     $rowNr++;
   380 
   381 # when adding new rows, previously entered values will be mapped onto the new table rows
   382 # when the last row is not the newly added, as may happen with footer rows, we need to adjust the mapping
   383 # we introduce a 'rowNr shift' for values
   384 # we assume that new rows are added just before the footer
   385                     my $shift = 0;
   386                     if ( $footerRowCount > 0 ) {
   387                         my $bodyRowNr = $rowNr - $headerRowCount;
   388                         if ( $bodyRowNr > ( $etrows - $addedRowCount ) ) {
   389                             $shift = $addedRowCountParam;
   390                         }
   391                     }
   392                     my $theRowNr = $rowNr + $shift;
   393                     my $isNewRow = 0;
   394 s/$regex{table_row}/handleTableRow( $1, $2, $tableNr, $isNewRow, $theRowNr, $doEdit, $doSave, $theWeb, $theTopic )/eo;
   395                     push @rows, $_;
   396                     next;
   397                 }
   398                 elsif ( $insideTable && !$hasTableTag ) {
   399 
   400                     # end of table
   401                     $endOfTable = 1;
   402                     my @headerRows = ();
   403                     my @footerRows = ();
   404                     my @bodyRows   = @rows;    #clone
   405 
   406                     if ( $headerRowCount > 0 ) {
   407                         @headerRows = @rows;    # clone
   408                         splice @headerRows, $headerRowCount;
   409 
   410                         # remove the header rows from the body rows
   411                         splice @bodyRows, 0, $headerRowCount;
   412                     }
   413                     if ( $footerRowCount > 0 ) {
   414                         @footerRows = @rows;    # clone
   415                         splice @footerRows, 0,
   416                           ( scalar @footerRows - $footerRowCount );
   417 
   418                         # remove the footer rows from the body rows
   419                         splice @bodyRows,
   420                           ( scalar @bodyRows - $footerRowCount ),
   421                           $footerRowCount;
   422                     }
   423 
   424                     # delete rows?
   425                     if ( $doEdit || $doSave ) {
   426                         if ( scalar @bodyRows > $etrows && $etrows != -1 ) {
   427                             splice( @bodyRows, $etrows );
   428                         }
   429                     }
   430 
   431                     # no table at all?
   432                     if ( $doEdit && !$doSave ) {
   433 
   434                         # if we are starting with an empty table, we force
   435                         # create a row, with an optional header row
   436                         my $addHeader =
   437                           ( $params{'header'} && $headerRowCount == 0 )
   438                           ? 1
   439                           : 0;
   440                         my $firstRowsCount = 1 + $addHeader;
   441 
   442                         if ( scalar @bodyRows < $firstRowsCount
   443                             && !$query->param('etdelrow') )
   444                         {
   445                             if ( $etrows < $firstRowsCount ) {
   446                                 $etrows = $firstRowsCount;
   447                             }
   448                         }
   449                     }
   450 
   451                     # add rows?
   452                     if ( $doEdit || $doSave ) {
   453                         while ( scalar @bodyRows < $etrows ) {
   454                             $rowNr++;
   455                             my $newBodyRowNr = scalar @bodyRows + 1;
   456                             my $theRowNr     = $newBodyRowNr + $headerRowCount;
   457 
   458                             my $isNewRow = ( defined $etrowsParam
   459                                   && $newBodyRowNr > $etrowsParam ) ? 1 : 0;
   460                             my $newRow = handleTableRow(
   461                                 '',        '',      $tableNr, $isNewRow,
   462                                 $theRowNr, $doEdit, $doSave,  $theWeb,
   463                                 $theTopic
   464                             );
   465                             push @bodyRows, $newRow;
   466                         }
   467                     }
   468 
   469                     my @combinedRows = ( @headerRows, @bodyRows, @footerRows );
   470 
   471                     # after re-ordering, renumber the cells
   472                     my $rowCounter = 0;
   473                     for my $cellRow (@combinedRows) {
   474                         $rowCounter++;
   475                         $cellRow =~
   476                           s/(etcell)([0-9]+)(x)([0-9]+)/$1$rowCounter$3$4/go;
   477                     }
   478                     $result .= join( "\n", @combinedRows ) . "\n";
   479 
   480                     if ( !$doSave ) {
   481                         my $rowCount = scalar @bodyRows;
   482                         my $tableEnd =
   483                           handleTableEnd( $theWeb, $rowCount, $doEdit,
   484                             $headerRowCount, $footerRowCount, $addedRowCount );
   485                         $result .= $tableEnd;
   486                     }
   487 
   488                 }    # $hasTableRow
   489             }    #/ if $doEdit
   490         }    # if $enableForm
   491 
   492         if ($endOfTable) {
   493             $endOfTable = 0;
   494 
   495             # re-init values
   496             $insideTable     = 0;
   497             $enableForm      = 0;
   498             $doEdit          = 0;
   499             $rowNr           = 0;
   500             $createdNewTable = 0;
   501             $headerRowCount  = 0;
   502             $footerRowCount  = 0;
   503             $etrows          = -1;
   504             @rows            = ();
   505             $isAtTheTable    = 0;
   506             $cgiTableNr      = 0;
   507         }
   508 
   509         $result .= "$_\n";
   510     }
   511 
   512     # clean up hack that handles EDITTABLE correctly if at end
   513     $result =~ s/($RENDER_HACK)+$//go;
   514 
   515     if ($doSave) {
   516         my $error = TWiki::Func::saveTopicText( $theWeb, $theTopic, $result, '',
   517             $doSaveQuiet );
   518         TWiki::Func::setTopicEditLock( $theWeb, $theTopic, 0 );   # unlock Topic
   519         my $url = TWiki::Func::getViewUrl( $theWeb, $theTopic );
   520         if ($error) {
   521             $url = TWiki::Func::getOopsUrl( $theWeb, $theTopic, 'oopssaveerr',
   522                 $error );
   523         }
   524         TWiki::Func::redirectCgiQuery( $query, $url );
   525         return;
   526     }
   527     $_[0] = $result;
   528 }
   529 
   530 =pod
   531 
   532 =cut
   533 
   534 sub extractParams {
   535     my ( $theArgs, $theHashRef ) = @_;
   536 
   537     my $tmp = TWiki::Func::extractNameValuePair( $theArgs, 'header' );
   538     $$theHashRef{'header'} = $tmp if ($tmp);
   539 
   540     $tmp = TWiki::Func::extractNameValuePair( $theArgs, 'footer' );
   541     $$theHashRef{'footer'} = $tmp if ($tmp);
   542 
   543     $tmp = TWiki::Func::extractNameValuePair( $theArgs, 'headerislabel' );
   544     $$theHashRef{'headerislabel'} = $tmp if ($tmp);
   545 
   546     $tmp = TWiki::Func::extractNameValuePair( $theArgs, 'format' );
   547     $tmp =~ s/^\s*\|*\s*//o;
   548     $tmp =~ s/\s*\|*\s*$//o;
   549     $$theHashRef{'format'} = $tmp if ($tmp);
   550 
   551     $tmp = TWiki::Func::extractNameValuePair( $theArgs, 'changerows' );
   552     $$theHashRef{'changerows'} = $tmp if ($tmp);
   553 
   554     $tmp = TWiki::Func::extractNameValuePair( $theArgs, 'quietsave' );
   555     $$theHashRef{'quietsave'} = $tmp if ($tmp);
   556 
   557     $tmp = TWiki::Func::extractNameValuePair( $theArgs, 'helptopic' );
   558     $$theHashRef{'helptopic'} = $tmp if ($tmp);
   559 
   560     $tmp = TWiki::Func::extractNameValuePair( $theArgs, 'editbutton' );
   561     $$theHashRef{'editbutton'} = $tmp if ($tmp);
   562 
   563     return;
   564 }
   565 
   566 =pod
   567 
   568 =cut
   569 
   570 sub parseFormat {
   571     my ( $theFormat, $theTopic, $theWeb, $doExpand ) = @_;
   572 
   573     #$theFormat =~ s/\$nop(\(\))?//gos;         # remove filler
   574     #$theFormat =~ s/\$quot(\(\))?/\"/gos;      # expand double quote
   575     #$theFormat =~ s/\$percnt(\(\))?/\%/gos;    # expand percent
   576     #$theFormat =~ s/\$dollar(\(\))?/\$/gos;    # expand dollar
   577 
   578     if ($doExpand) {
   579 
   580         # expanded form to be able to use %-vars in format
   581         $theFormat =~ s/<nop>//gos;
   582         $theFormat =
   583           TWiki::Func::expandCommonVariables( $theFormat, $theTopic, $theWeb );
   584     }
   585     my @aFormat = split( /\s*\|\s*/, $theFormat );
   586     $aFormat[0] = "text,$DEFAULT_FIELD_SIZE" unless @aFormat;
   587 
   588     return @aFormat;
   589 }
   590 
   591 =pod
   592 
   593 =cut
   594 
   595 sub handleEditTableTag {
   596     my ( $theWeb, $theTopic, $thePreSpace, $theArgs ) = @_;
   597 
   598     #$preSp = $thePreSpace || '';
   599 
   600     %params = (
   601         'header'        => '',
   602         'footer'        => '',
   603         'headerislabel' => "1",
   604         'format'        => '',
   605         'changerows'    => $prefCHANGEROWS,
   606         'quietsave'     => $prefQUIETSAVE,
   607         'helptopic'     => '',
   608         'editbutton'    => '',
   609     );
   610     $warningMessage = '';
   611 
   612     # include topic to read definitions
   613     my $iTopic = TWiki::Func::extractNameValuePair( $theArgs, 'include' );
   614     my $iTopicExists = 0;
   615     if ($iTopic) {
   616         if ( $iTopic =~ /^([^\.]+)\.(.*)$/o ) {
   617             $theWeb = $1;
   618             $iTopic = $2;
   619         }
   620 
   621         $iTopicExists = TWiki::Func::topicExists( $theWeb, $iTopic )
   622           if $iTopic ne '';
   623         if ( $iTopic && !$iTopicExists ) {
   624             $warningMessage = $prefMESSAGE_INCLUDED_TOPIC_DOES_NOT_EXIST;
   625         }
   626         if ($iTopicExists) {
   627 
   628             my $text = TWiki::Func::readTopicText( $theWeb, $iTopic );
   629             $text =~ /$regex{edit_table_plugin}/os;
   630             if ($1) {
   631                 my $args = $1;
   632                 if (   $theWeb ne $TWiki::Plugins::EditTablePlugin::web
   633                     || $iTopic ne $TWiki::Plugins::EditTablePlugin::topic )
   634                 {
   635 
   636                     # expand common vars, unless oneself to prevent recursion
   637                     $args = TWiki::Func::expandCommonVariables( $1, $iTopic,
   638                         $theWeb );
   639                 }
   640                 extractParams( $args, \%params );
   641             }
   642         }
   643     }
   644 
   645     extractParams( $theArgs, \%params );
   646 
   647     # FIXME: should use TWiki::Func::extractParameters
   648     $params{'header'} = '' if ( $params{header} =~ /^(off|no)$/oi );
   649     $params{'header'} =~ s/^\s*\|//o;
   650     $params{'header'} =~ s/\|\s*$//o;
   651     $params{'headerislabel'} = ''
   652       if ( $params{headerislabel} =~ /^(off|no)$/oi );
   653     $params{'footer'} = '' if ( $params{footer} =~ /^(off|no)$/oi );
   654     $params{'footer'} =~ s/^\s*\|//o;
   655     $params{'footer'} =~ s/\|\s*$//o;
   656     $params{'changerows'} = '' if ( $params{changerows} =~ /^(off|no)$/oi );
   657     $params{'quietsave'}  = '' if ( $params{quietsave}  =~ /^(off|no)$/oi );
   658 
   659     @format         = parseFormat( $params{format}, $theTopic, $theWeb, 0 );
   660     @formatExpanded = parseFormat( $params{format}, $theTopic, $theWeb, 1 );
   661     $nrCols         = @format;
   662 
   663     return "$preSp";
   664 }
   665 
   666 =pod
   667 
   668 =cut
   669 
   670 sub handleTableStart {
   671     my ( $theWeb, $theTopic, $theTableNr, $doEdit ) = @_;
   672     my $viewUrl = TWiki::Func::getScriptUrl( $theWeb, $theTopic, 'viewauth' )
   673       . "\#edittable$theTableNr";
   674     my $text = '';
   675     if ($doEdit) {
   676         require TWiki::Contrib::JSCalendarContrib;
   677         unless ($@) {
   678             TWiki::Contrib::JSCalendarContrib::addHEAD('twiki');
   679         }
   680     }
   681     $text .= "$preSp<noautolink>\n" if $doEdit;
   682     $text .= "$preSp<a name=\"edittable$theTableNr\"></a>\n";
   683     my $cssClass = 'editTable';
   684     if ($doEdit) {
   685         $cssClass .= ' editTableEdit';
   686     }
   687     $text .= "<div class=\"" . $cssClass . "\">\n";
   688     $text .=
   689 "$preSp<form name=\"edittable$theTableNr\" action=\"$viewUrl\" method=\"post\">\n";
   690     $text .= hiddenField( $preSp, 'ettablenr', $theTableNr, "\n" );
   691     $text .= hiddenField( $preSp, 'etedit', 'on', "\n" )
   692       unless $doEdit;
   693     return $text;
   694 }
   695 
   696 sub hiddenField {
   697     my ( $prefix, $name, $value, $suffix ) = @_;
   698     $prefix = defined $prefix ? $prefix : '';
   699     $suffix = defined $suffix ? $suffix : '';
   700     return
   701       "$prefix<input type=\"hidden\" name=\"$name\" value=\"$value\" />$suffix";
   702 }
   703 
   704 =pod
   705 
   706 =cut
   707 
   708 sub handleTableEnd {
   709     my ( $theWeb, $rowCount, $doEdit, $headerRowCount, $footerRowCount,
   710         $addedRowCount )
   711       = @_;
   712     my $text = '';
   713     $text .= hiddenField( $preSp, 'etrows',       $rowCount,       "\n" );
   714     $text .= hiddenField( $preSp, 'etheaderrows', $headerRowCount, "\n" )
   715       if $headerRowCount;
   716     $text .= hiddenField( $preSp, 'etfooterrows', $footerRowCount, "\n" )
   717       if $footerRowCount;
   718     $text .= hiddenField( $preSp, 'etaddedrows', $addedRowCount, "\n" )
   719       if $addedRowCount;
   720 
   721     $text .= hiddenField( $preSp, 'sort', 'off', "\n" );
   722 
   723     if ($doEdit) {
   724 
   725         # Edit mode
   726         $text .=
   727 "$preSp<input type=\"submit\" name=\"etsave\" id=\"etsave\" value=\"$prefSAVE_BUTTON\" class=\"twikiSubmit\" />\n";
   728         if ( $params{'quietsave'} ) {
   729             $text .=
   730 "$preSp<input type=\"submit\" name=\"etqsave\" id=\"etqsave\" value=\"$prefQUIET_SAVE_BUTTON\" class=\"twikiButton\" />\n";
   731         }
   732         if ( $params{'changerows'} ) {
   733             $text .=
   734 "$preSp<input type=\"submit\" name=\"etaddrow\" id=\"etaddrow\" value=\"$prefADD_ROW_BUTTON\" class=\"twikiButton\" />\n";
   735             $text .=
   736 "$preSp<input type=\"submit\" name=\"etdelrow\" id=\"etdelrow\" value=\"$prefDELETE_LAST_ROW_BUTTON\" class=\"twikiButton\" />\n"
   737               unless ( $params{'changerows'} =~ /^add$/oi );
   738         }
   739         $text .=
   740 "$preSp<input type=\"submit\" name=\"etcancel\" id=\"etcancel\" value=\"$prefCANCEL_BUTTON\" class=\"twikiButton twikiButtonCancel\" />\n";
   741 
   742         if ( $params{'helptopic'} ) {
   743 
   744             # read help topic and show below the table
   745             if ( $params{'helptopic'} =~ /^([^\.]+)\.(.*)$/o ) {
   746                 $theWeb = $1;
   747                 $params{'helptopic'} = $2;
   748             }
   749             my $helpText =
   750               TWiki::Func::readTopicText( $theWeb, $params{'helptopic'} );
   751 
   752             #Strip out the meta data so it won't be displayed.
   753             $helpText =~ s/%META:[A-Za-z0-9]+{.*?}%//g;
   754             if ($helpText) {
   755                 $helpText =~ s/.*?%STARTINCLUDE%//os;
   756                 $helpText =~ s/%STOPINCLUDE%.*//os;
   757                 $text .= $helpText;
   758             }
   759         }
   760         my $assetUrl = '%PUBURL%/%TWIKIWEB%/EditTablePlugin';
   761 
   762         # table specific script
   763         my $tableNr = $query->param('ettablenr');
   764         &TWiki::Plugins::EditTablePlugin::addEditModeHeadersToHead( $tableNr,
   765             $assetUrl );
   766     }
   767     else {
   768         $params{editbutton} |= '';
   769 
   770         # View mode
   771         if ( $params{editbutton} eq "hide" ) {
   772 
   773             # do nothing, button assumed to be in a cell
   774         }
   775         else {
   776 
   777             # Add edit button to end of table
   778             $text .=
   779               $preSp . viewEditCell("editbutton, 1, $params{'editbutton'}");
   780         }
   781     }
   782     $text .= "$preSp</form>\n";
   783     $text .= "</div><!-- /editTable -->";
   784     $text .= "$preSp</noautolink>\n" if $doEdit;
   785     $text .= "\n";
   786     return $text;
   787 }
   788 
   789 =pod
   790 
   791 =cut
   792 
   793 sub parseEditCellFormat {
   794     $_[1] = TWiki::Func::extractNameValuePair( $_[0] );
   795     return '';
   796 }
   797 
   798 =pod
   799 
   800 =cut
   801 
   802 sub viewEditCell {
   803     my ($theAttr) = @_;
   804     $theAttr = TWiki::Func::extractNameValuePair($theAttr);
   805     return '' unless ( $theAttr =~ /^editbutton/ );
   806 
   807     $params{editbutton} = 'hide'
   808       unless ( $params{editbutton} );    # Hide below table edit button
   809 
   810     my @bits = split( /,\s*/, $theAttr );
   811     my $value = '';
   812     $value = $bits[2] if ( @bits > 2 );
   813     my $img = '';
   814     $img = $bits[3] if ( @bits > 3 );
   815 
   816     unless ($value) {
   817         $value = $prefEDIT_BUTTON || '';
   818         $img = '';
   819         if ( $value =~ s/(.+),\s*(.+)/$1/o ) {
   820             $img = $2;
   821             $img =~ s|%ATTACHURL%|%PUBURL%/%TWIKIWEB%/EditTablePlugin|o;
   822             $img =~ s|%WEB%|%TWIKIWEB%|o;
   823         }
   824     }
   825     if ($img) {
   826         return
   827 "<input class=\"editTableEditImageButton\" type=\"image\" src=\"$img\" alt=\"$value\" /> $warningMessage";
   828     }
   829     else {
   830         return
   831 "<input class=\"twikiButton editTableEditButton\" type=\"submit\" value=\"$value\" /> $warningMessage";
   832     }
   833 }
   834 
   835 =pod
   836 
   837 =cut
   838 
   839 sub saveEditCellFormat {
   840     my ( $theFormat, $theName ) = @_;
   841     return '' unless ($theFormat);
   842     $theName =~ s/cell/format/;
   843     return hiddenField( '', $theName, $theFormat, '' );
   844 }
   845 
   846 =pod
   847 
   848 digestedCellValue: properly handle labels whose rows may have been moved around by javascript, and therefore no longer correspond to the raw saved table text.
   849 
   850 =cut
   851 
   852 sub inputElement {
   853     my ( $theTableNr, $theRowNr, $theCol, $theName, $theValue,
   854         $digestedCellValue, $theWeb, $theTopic )
   855       = @_;
   856 
   857     my $rawValue = $theValue;
   858     my $text     = '';
   859     my $i        = @format - 1;
   860     $i = $theCol if ( $theCol < $i );
   861 
   862     my @bits         = split( /,\s*/, $format[$i] );
   863     my @bitsExpanded = split( /,\s*/, $formatExpanded[$i] );
   864 
   865     my $cellFormat = '';
   866     $theValue =~
   867       s/\s*%EDITCELL{(.*?)}%/&parseEditCellFormat( $1, $cellFormat )/eo;
   868 
   869     # If cell is empty we remove the space to not annoy the user when
   870     # he needs to add text to empty cell.
   871     $theValue = '' if ( $theValue eq ' ' );
   872     
   873     if ($cellFormat) {
   874         my @aFormat = parseFormat( $cellFormat, $theTopic, $theWeb, 0 );
   875         @bits = split( /,\s*/, $aFormat[0] );
   876         @aFormat = parseFormat( $cellFormat, $theTopic, $theWeb, 1 );
   877         @bitsExpanded = split( /,\s*/, $aFormat[0] );
   878     }
   879 
   880     my $type = 'text';
   881     $type = $bits[0] if @bits > 0;
   882 
   883     # a table header is considered a label if read only header flag set
   884     $type = 'label'
   885       if ( ( $params{'headerislabel'} ) && ( $theValue =~ /^\s*\*.*\*\s*$/ ) );
   886     $type = 'label' if ( $type eq 'editbutton' );    # Hide [Edit table] button
   887     my $size = 0;
   888     $size = $bits[1] if @bits > 1;
   889     my $val         = '';
   890     my $valExpanded = '';
   891     my $sel         = '';
   892     if ( $type eq 'select' ) {
   893         my $expandedValue =
   894           TWiki::Func::expandCommonVariables( $theValue, $theTopic, $theWeb );
   895         $size = 1 if $size < 1;
   896         $text =
   897           "<select class=\"twikiSelect\" name=\"$theName\" size=\"$size\">";
   898         $i = 2;
   899         while ( $i < @bits ) {
   900             $val         = $bits[$i]         || '';
   901             $valExpanded = $bitsExpanded[$i] || '';
   902             $expandedValue =~ s/^\s+//;
   903             $expandedValue =~ s/\s+$//;
   904             $valExpanded   =~ s/^\s+//;
   905             $valExpanded   =~ s/\s+$//;
   906 
   907             if ( $valExpanded eq $expandedValue ) {
   908                 $text .= " <option selected=\"selected\">$val</option>";
   909             }
   910             else {
   911                 $text .= " <option>$val</option>";
   912             }
   913             $i++;
   914         }
   915         $text .= "</select>";
   916         $text .= saveEditCellFormat( $cellFormat, $theName );
   917 
   918     }
   919     elsif ( $type eq "radio" ) {
   920         my $expandedValue =
   921           &TWiki::Func::expandCommonVariables( $theValue, $theTopic, $theWeb );
   922         $size = 1 if $size < 1;
   923         my $elements = ( @bits - 2 );
   924         my $lines    = $elements / $size;
   925         $lines = ( $lines == int($lines) ) ? $lines : int( $lines + 1 );
   926         $text .= "<table class=\"editTableInnerTable\"><tr><td valign=\"top\">"
   927           if ( $lines > 1 );
   928         $i = 2;
   929         while ( $i < @bits ) {
   930             $val         = $bits[$i]         || "";
   931             $valExpanded = $bitsExpanded[$i] || "";
   932             $expandedValue =~ s/^\s+//;
   933             $expandedValue =~ s/\s+$//;
   934             $valExpanded   =~ s/^\s+//;
   935             $valExpanded   =~ s/\s+$//;
   936             $text .= "<input type=\"radio\" name=\"$theName\" value=\"$val\"";
   937 
   938             # make space to expand variables
   939             $val = addSpaceToBothSides($val);
   940             $text .= " checked=\"checked\""
   941               if ( $valExpanded eq $expandedValue );
   942             $text .= " />$val";
   943             if ( $lines > 1 ) {
   944 
   945                 if ( ( $i - 1 ) % $lines ) {
   946                     $text .= "<br />";
   947                 }
   948                 elsif ( $i - 1 < $elements ) {
   949                     $text .= "</td><td valign=\"top\">";
   950                 }
   951             }
   952             $i++;
   953         }
   954         $text .= "</td></tr></table>" if ( $lines > 1 );
   955         $text .= saveEditCellFormat( $cellFormat, $theName );
   956 
   957     }
   958     elsif ( $type eq "checkbox" ) {
   959         my $expandedValue =
   960           &TWiki::Func::expandCommonVariables( $theValue, $theTopic, $theWeb );
   961         $size = 1 if $size < 1;
   962         my $elements = ( @bits - 2 );
   963         my $lines    = $elements / $size;
   964         my $names    = "Chkbx:";
   965         $lines = ( $lines == int($lines) ) ? $lines : int( $lines + 1 );
   966         $text .= "<table class=\"editTableInnerTable\"><tr><td valign=\"top\">"
   967           if ( $lines > 1 );
   968         $i = 2;
   969 
   970         while ( $i < @bits ) {
   971             $val         = $bits[$i]         || "";
   972             $valExpanded = $bitsExpanded[$i] || "";
   973             $expandedValue =~ s/^\s+//;
   974             $expandedValue =~ s/\s+$//;
   975             $valExpanded   =~ s/^\s+//;
   976             $valExpanded   =~ s/\s+$//;
   977             $names .= " ${theName}x$i";
   978             $text .=
   979               " <input type=\"checkbox\" name=\"${theName}x$i\" value=\"$val\"";
   980 
   981             $val = addSpaceToBothSides($val);
   982 
   983             $text .= " checked=\"checked\""
   984               if ( $expandedValue =~ /(^|\s*,\s*)\Q$valExpanded\E(\s*,\s*|$)/ );
   985             $text .= " />$val";
   986 
   987             if ( $lines > 1 ) {
   988                 if ( ( $i - 1 ) % $lines ) {
   989                     $text .= "<br />";
   990                 }
   991                 elsif ( $i - 1 < $elements ) {
   992                     $text .= "</td><td valign=\"top\">";
   993                 }
   994             }
   995             $i++;
   996         }
   997         $text .= "</td></tr></table>" if ( $lines > 1 );
   998         $text .= hiddenField( $preSp, $theName, $names );
   999         $text .= saveEditCellFormat( $cellFormat, $theName, "\n" );
  1000 
  1001     }
  1002     elsif ( $type eq 'row' ) {
  1003         $size = $size + $theRowNr;
  1004         $text =
  1005             "<span class=\"et_rowlabel\">"
  1006           . hiddenField( $size, $theName, $size )
  1007           . "</span>";
  1008         $text .= saveEditCellFormat( $cellFormat, $theName );
  1009 
  1010     }
  1011     elsif ( $type eq 'label' ) {
  1012 
  1013         # show label text as is, and add a hidden field with value
  1014         my $isHeader = 0;
  1015         $isHeader = 1 if ( $theValue =~ s/^\s*\*(.*)\*\s*$/$1/o );
  1016         $text = $theValue;
  1017 
  1018         # To optimize things, only in the case where a read-only column is
  1019         # being processed (inside of this unless() statement) do we actually
  1020         # go out and read the original topic.  Thus the reason for the
  1021         # following unless() so we only read the topic the first time through.
  1022         unless ( defined $table and $digestedCellValue ) {
  1023 
  1024             # To deal with the situation where TWiki variables, like
  1025             # %CALC%, have already been processed and end up getting saved
  1026             # in the table that way (processed), we need to read in the
  1027             # topic page in raw format
  1028             my $topicContents = TWiki::Func::readTopicText(
  1029                 $TWiki::Plugins::EditTablePlugin::web,
  1030                 $TWiki::Plugins::EditTablePlugin::topic
  1031             );
  1032             $table = TWiki::Plugins::Table->new($topicContents);
  1033         }
  1034         my $cell =
  1035             $digestedCellValue
  1036           ? $table->getCell( $theTableNr, $theRowNr - 1, $theCol )
  1037           : $rawValue;
  1038         $theValue = $cell if ( defined $cell );    # original value from file
  1039         $theValue = TWiki::Plugins::EditTablePlugin::encodeValue($theValue)
  1040           unless ( $theValue eq '' );
  1041         $theValue = "\*$theValue\*" if ( $isHeader and $digestedCellValue );
  1042         $text .= hiddenField( $preSp, $theName, $theValue );
  1043         $text = "\*$text\*" if ($isHeader);
  1044 
  1045     }
  1046     elsif ( $type eq 'textarea' ) {
  1047         my ( $rows, $cols ) = split( /x/, $size );
  1048 
  1049         $rows |= 3  if !defined $rows;
  1050         $cols |= 30 if !defined $cols;
  1051         $theValue = TWiki::Plugins::EditTablePlugin::encodeValue($theValue)
  1052           unless ( $theValue eq '' );
  1053         $text .=
  1054 "<textarea class=\"twikiTextarea editTableTextarea\" rows=\"$rows\" cols=\"$cols\" name=\"$theName\">$theValue</textarea>";
  1055         $text .= saveEditCellFormat( $cellFormat, $theName );
  1056 
  1057     }
  1058     elsif ( $type eq 'date' ) {
  1059         my $ifFormat = '';
  1060         $ifFormat = $bits[3] if ( @bits > 3 );
  1061         $ifFormat ||= $TWiki::cfg{JSCalendarContrib}{format} || '%e %B %Y';
  1062         $size = 10 if ( !$size || $size < 1 );
  1063         $theValue = TWiki::Plugins::EditTablePlugin::encodeValue($theValue)
  1064           unless ( $theValue eq '' );
  1065         $text .= CGI::textfield(
  1066             {
  1067                 name     => $theName,
  1068                 class    => 'twikiInputField editTableInput',
  1069                 id       => 'id' . $theName,
  1070                 size     => $size,
  1071                 value    => $theValue,
  1072                 override => 1
  1073             }
  1074         );
  1075         $text .= saveEditCellFormat( $cellFormat, $theName );
  1076         eval 'use TWiki::Contrib::JSCalendarContrib';
  1077 
  1078         unless ($@) {
  1079             $text .= '<span class="twikiMakeVisible">';
  1080             $text .= CGI::image_button(
  1081                 -class   => 'editTableCalendarButton',
  1082                 -name    => 'calendar',
  1083                 -onclick => "return showCalendar('id$theName','$ifFormat')",
  1084                 -src     => TWiki::Func::getPubUrlPath() . '/'
  1085                   . TWiki::Func::getTwikiWebname()
  1086                   . '/JSCalendarContrib/img.gif',
  1087                 -alt   => 'Calendar',
  1088                 -align => 'middle'
  1089             );
  1090             $text .= '</span>';
  1091         }
  1092         $query->{'jscalendar'} = 1;
  1093     }
  1094     else {    #  if( $type eq 'text')
  1095         $size = $DEFAULT_FIELD_SIZE if $size < 1;
  1096         $theValue = TWiki::Plugins::EditTablePlugin::encodeValue($theValue)
  1097           unless ( $theValue eq '' );
  1098         $text =
  1099 "<input class=\"twikiInputField editTableInput\" type=\"text\" name=\"$theName\" size=\"$size\" value=\"$theValue\" />";
  1100         $text .= saveEditCellFormat( $cellFormat, $theName );
  1101     }
  1102 
  1103     if ( $type ne 'textarea' ) {
  1104         $text =~
  1105           s/&#10;/<br \/>/go;    # change unicode linebreak character to <br />
  1106     }
  1107     return $text;
  1108 }
  1109 
  1110 =pod
  1111 
  1112 =cut
  1113 
  1114 sub handleTableRow {
  1115     my (
  1116         $thePre, $theRow, $theTableNr, $isNewRow, $theRowNr,
  1117         $doEdit, $doSave, $theWeb,     $theTopic
  1118     ) = @_;
  1119     $thePre |= '';
  1120     my $text = "$thePre\|";
  1121     if ($doEdit) {
  1122         $theRow =~ s/\|\s*$//o;
  1123         my $rowID = $query->param("etrow_id$theRowNr");
  1124         $rowID = $theRowNr if !defined $rowID;
  1125         my @cells = split( /\|/, $theRow );
  1126         my $tmp = @cells;
  1127         $nrCols = $tmp if ( $tmp > $nrCols );    # expand number of cols
  1128         my $val         = '';
  1129         my $cellFormat  = '';
  1130         my $cell        = '';
  1131         my $digested    = 0;
  1132         my $cellDefined = 0;
  1133         my $col         = 0;
  1134 
  1135         while ( $col < $nrCols ) {
  1136             $col += 1;
  1137             $cellDefined = 0;
  1138             $val = $isNewRow ? undef : $query->param("etcell${rowID}x$col");
  1139             if ( $val && $val =~ /^Chkbx: (etcell.*)/ ) {
  1140 
  1141       # Multiple checkboxes, val has format "Chkbx: etcell4x2x2 etcell4x2x3 ..."
  1142                 my $chkBoxeNames = $1;
  1143                 my $chkBoxVals   = "";
  1144                 foreach ( split( /\s/, $chkBoxeNames ) ) {
  1145                     $val = $query->param($_);
  1146 
  1147                     #$chkBoxVals .= "$val," if ( defined $val );
  1148                     if ( defined $val ) {
  1149 
  1150                         # make space to expand variables
  1151                         $val = addSpaceToBothSides($val);
  1152                         $chkBoxVals .= $val . ',';
  1153                     }
  1154                 }
  1155                 $chkBoxVals =~ s/,\s*$//;
  1156                 $val = $chkBoxVals;
  1157             }
  1158             $cellFormat = $query->param("etformat${rowID}x$col");
  1159             $val .= " %EDITCELL{$cellFormat}%" if ($cellFormat);
  1160             if ( defined $val ) {
  1161 
  1162                 # change any new line character sequences to <br />
  1163                 $val =~ s/[\n\r]{2,}?/%BR%/gos;
  1164 
  1165                 # escape "|" to HTML entity
  1166                 $val =~ s/\|/\&\#124;/gos;
  1167                 $cellDefined = 1;
  1168 
  1169                 # Expand %-vars
  1170                 $cell = $val;
  1171             }
  1172             elsif ( $col <= @cells ) {
  1173                 $cell = $cells[ $col - 1 ];
  1174                 $digested = 1;    # Flag that we are using non-raw cell text.
  1175                 $cellDefined = 1 if ( length($cell) > 0 );
  1176                 $cell =~ s/^\s*(.+?)\s*$/$1/o
  1177                   ; # remove spaces around content, but do not void a cell with just spaces
  1178             }
  1179             else {
  1180                 $cell = '';
  1181             }
  1182 
  1183             if ( ( $theRowNr <= 1 ) && ( $params{'header'} ) ) {
  1184                 unless ($cell) {
  1185                     if ( $params{'header'} =~ /^on$/i ) {
  1186                         if (   ( @format >= $col )
  1187                             && ( $format[ $col - 1 ] =~ /(.*?)\,/ ) )
  1188                         {
  1189                             $cell = $1;
  1190                         }
  1191                         $cell = 'text' unless $cell;
  1192                         $cell = "*$cell*";
  1193                     }
  1194                     else {
  1195                         my @hCells = split( /\|/, $params{'header'} );
  1196                         $cell = $hCells[ $col - 1 ] if ( @hCells >= $col );
  1197                         $cell = "*text*" unless $cell;
  1198                     }
  1199                 }
  1200                 $cell = addSpaceToBothSides($cell);
  1201                 $text .= "$cell\|";
  1202             }
  1203             elsif ($doSave) {
  1204                 $cell = addSpaceToBothSides($cell);
  1205                 
  1206                 # Item5217 Avoid that deleting content of cell creates unwanted span
  1207                 $cell = ' ' if $cell eq '';
  1208                 
  1209                 $text .= "$cell\|";
  1210             }
  1211             else {
  1212                 if (
  1213                        ( !$cellDefined )
  1214                     && ( @format >= $col )
  1215                     && ( $format[ $col - 1 ] =~
  1216                         /^\s*(.*?)\,\s*(.*?)\,\s*(.*?)\s*$/ )
  1217                   )
  1218                 {
  1219 
  1220                     # default value of "| text, 20, a, b, c |" cell is "a, b, c"
  1221                     # default value of '| select, 1, a, b, c |' cell is "a"
  1222                     $val  = $1;    # type
  1223                     $cell = $3;
  1224                     $cell = ''
  1225                       unless ( defined $cell && $cell ne '' )
  1226                       ;            # Proper handling of '0'
  1227                     $cell =~ s/\,.*$//o
  1228                       if ( $val eq 'select' || $val eq 'date' );
  1229                 }
  1230                 my $element = '';
  1231                 $element =
  1232                   inputElement( $theTableNr, $theRowNr, $col - 1,
  1233                     "etcell${theRowNr}x$col", $cell, $digested, $theWeb,
  1234                     $theTopic );
  1235                 $element = " $element \|";
  1236                 $text .= $element;
  1237             }
  1238         }
  1239     }
  1240     else {
  1241         $theRow =~ s/%EDITCELL{(.*?)}%/viewEditCell($1)/geo;
  1242         $text .= $theRow;
  1243     }
  1244 
  1245     # render final value in view mode (not edit or save)
  1246     TWiki::Plugins::EditTablePlugin::decodeFormatTokens($text)
  1247       if ( !$doSave && !$doEdit );
  1248 
  1249     # put one space before linebreak (but not more than one)
  1250     # so TML can get expanded
  1251     $text =~ s/\s*<br \/>/ <br \/>/go;
  1252     return $text;
  1253 }
  1254 
  1255 =pod
  1256 
  1257 Add one space to both sides of the text to allow TML expansion.
  1258 Convert multiple (existing) spaces to one space.
  1259 
  1260 =cut
  1261 
  1262 sub addSpaceToBothSides {
  1263     my ($text) = @_;
  1264     return $text if $text eq '';
  1265 
  1266     $text = " $text ";
  1267     $text =~ s/^\s+/ /;    # remove extra spaces
  1268     $text =~ s/\s+$/ /;
  1269     return $text;
  1270 }
  1271 
  1272 =pod
  1273 
  1274 =cut
  1275 
  1276 sub doCancelEdit {
  1277     my ( $theWeb, $theTopic ) = @_;
  1278 
  1279     TWiki::Func::writeDebug(
  1280         "- EditTablePlugin::doCancelEdit( $theWeb, $theTopic )")
  1281       if $TWiki::Plugins::EditTablePlugin::debug;
  1282 
  1283     TWiki::Func::setTopicEditLock( $theWeb, $theTopic, 0 );
  1284 
  1285     TWiki::Func::redirectCgiQuery( $query,
  1286         TWiki::Func::getViewUrl( $theWeb, $theTopic ) );
  1287 }
  1288 
  1289 =pod
  1290 
  1291 =cut
  1292 
  1293 sub doEnableEdit {
  1294     my ( $theWeb, $theTopic, $doCheckIfLocked ) = @_;
  1295 
  1296     TWiki::Func::writeDebug(
  1297         "- EditTablePlugin::doEnableEdit( $theWeb, $theTopic )")
  1298       if $TWiki::Plugins::EditTablePlugin::debug;
  1299 
  1300     my $wikiUserName = TWiki::Func::getWikiName();
  1301     if (
  1302         !TWiki::Func::checkAccessPermission(
  1303             'change', $wikiUserName, undef, $theTopic, $theWeb
  1304         )
  1305       )
  1306     {
  1307 
  1308         # user has no permission to change the topic
  1309         throw TWiki::OopsException(
  1310             'accessdenied',
  1311             def    => 'topic_access',
  1312             web    => $theWeb,
  1313             topic  => $theTopic,
  1314             params => [ 'change', 'denied' ]
  1315         );
  1316     }
  1317 
  1318     my $breakLock = $query->param('breaklock') || '';
  1319     unless ($breakLock) {
  1320         my ( $oopsUrl, $lockUser ) =
  1321           TWiki::Func::checkTopicEditLock( $theWeb, $theTopic, 'view' );
  1322         if ($oopsUrl) {
  1323             my $loginUser = TWiki::Func::wikiToUserName($wikiUserName);
  1324             if ( $lockUser ne $loginUser ) {
  1325 
  1326                 # change the default oopsleaseconflict url
  1327                 # use viewauth instead of view
  1328                 $oopsUrl =~ s/param4=view/param4=viewauth/;
  1329 
  1330                 # add info of the edited table
  1331                 my $params = '';
  1332                 $query = TWiki::Func::getCgiQuery();
  1333                 $params .= ';ettablenr=' . $query->param('ettablenr');
  1334                 $params .= ';etedit=on';
  1335                 $oopsUrl =~ s/($|#\w*)/$params/;
  1336 
  1337                 # warn user that other person is editing this topic
  1338                 TWiki::Func::redirectCgiQuery( $query, $oopsUrl );
  1339                 return 0;
  1340             }
  1341         }
  1342     }
  1343 
  1344     # We are allowed to edit
  1345     TWiki::Func::setTopicEditLock( $theWeb, $theTopic, 1 );
  1346 
  1347     return 1;
  1348 }
  1349 
  1350 package TWiki::Plugins::Table;
  1351 
  1352 use vars qw(
  1353   %regex
  1354 );
  1355 $regex{edit_table_plugin} = '%EDITTABLE{(.*?)}%';
  1356 
  1357 =pod
  1358 
  1359 =cut
  1360 
  1361 sub new {
  1362     my ( $class, $topicContents ) = @_;
  1363     my $this = {};
  1364     bless $this, $class;
  1365     $this->_parseOutTables($topicContents);
  1366     return $this;
  1367 }
  1368 
  1369 =pod
  1370 
  1371 TODO: this is currently only used for label tags, so this seams a lot of overhead for such a small thing
  1372 
  1373 The guts of this routine was initially copied from SpreadSheetPlugin.pm
  1374 and were used in the ChartPlugin Table object which this was copied from,
  1375 but this has been modified to support the functionality needed by the
  1376 EditTablePlugin.  One major change is to only count and save tables
  1377 following an %EDITTABLE{.*}% tag.
  1378 
  1379 This routine basically returns an array of hashes where each hash
  1380 contains the information for a single table.  Thus the first hash in the
  1381 array represents the first table found on the topic page, the second hash
  1382 in the array represents the second table found on the topic page, etc.
  1383 
  1384 =cut
  1385 
  1386 sub _parseOutTables {
  1387     my ( $this, $topic ) = @_;
  1388     my $tableNum = 1;    # Table number (only count tables with EDITTABLE tag)
  1389     my @tableMatrix;     # Currently parsed table.
  1390 
  1391     my $inEditTable = 0; # Flag to keep track if in an EDITTABLE table
  1392     my $insidePRE   = 0;
  1393     my $insideTABLE = 0;
  1394     my $line        = '';
  1395     my @row         = ();
  1396 
  1397     foreach ( split( /\n/, $topic ) ) {
  1398 
  1399         # change state:
  1400         m|<pre\b|i      && ( $insidePRE = 1 );
  1401         m|<verbatim\b|i && ( $insidePRE = 1 );
  1402         m|</pre>|i      && ( $insidePRE = 0 );
  1403         m|</verbatim>|i && ( $insidePRE = 0 );
  1404 
  1405         if ( !$insidePRE ) {
  1406             $inEditTable = 1 if (/$regex{edit_table_plugin}/);
  1407             if ($inEditTable) {
  1408                 if (/^\s*\|.*\|\s*$/) {
  1409 
  1410                     # inside | table |
  1411                     $insideTABLE = 1;
  1412                     $line        = $_;
  1413                     $line =~ s/^(\s*\|)(.*)\|\s*$/$2/o;    # Remove starting '|'
  1414                     @row = split( /\|/o, $line, -1 );
  1415                     _trim( \@row );
  1416                     push( @tableMatrix, [@row] );
  1417 
  1418                 }
  1419                 else {
  1420 
  1421                     # outside | table |
  1422                     if ($insideTABLE) {
  1423 
  1424                         # We were inside a table and are now outside of it so
  1425                         # save the table info into the Table object.
  1426                         $insideTABLE = 0;
  1427                         $inEditTable = 0;
  1428                         if ( @tableMatrix != 0 ) {
  1429 
  1430                             # Save the table via its table number
  1431                             $$this{"TABLE_$tableNum"} = [@tableMatrix];
  1432                             $tableNum++;
  1433                         }
  1434                         undef @tableMatrix;    # reset table matrix
  1435                     }
  1436                 }
  1437             }
  1438         }
  1439     }
  1440     $$this{NUM_TABLES} = $tableNum;
  1441 }
  1442 
  1443 =pod
  1444 
  1445 Trim any leading and trailing white space and/or '*'.
  1446 
  1447 =cut
  1448 
  1449 sub _trim {
  1450     my ($totrim) = @_;
  1451     for my $element (@$totrim) {
  1452         $element =~ s/^[\s\*]+//;    # Strip off leading white
  1453         $element =~ s/[\s\*]+$//;    # Strip off trailing white
  1454     }
  1455 }
  1456 
  1457 =pod
  1458 
  1459 Return the contents of the specified cell
  1460 
  1461 =cut
  1462 
  1463 sub getCell {
  1464     my ( $this, $tableNum, $row, $column ) = @_;
  1465 
  1466     my @selectedTable = $this->getTable($tableNum);
  1467     my $value         = $selectedTable[$row][$column];
  1468     return $value;
  1469 }
  1470 
  1471 =pod
  1472 
  1473 =cut
  1474 
  1475 sub getTable {
  1476     my ( $this, $tableNumber ) = @_;
  1477     my $table = $$this{"TABLE_$tableNumber"};
  1478     return @$table if defined($table);
  1479     return ();
  1480 }
  1481 
  1482 1;