1# See bottom of file for license and copyright information
2
3=pod
4
5Work in progress: changing the table based on form parameters
6
7(sub handleTableChangeParams)
8
9Test in topic:
10
11%EDITTABLE{}%
12| *AAA* |
13
14---++ Add rows from table 1
15<form name="edittable1" action="%SCRIPTURL{"viewauth"}%/Sandbox/EditTablePluginTest2#edittable1" method="post">
16<input type="hidden" name="ettablenr" value="1" />
17Position: <input type="text" class="foswikiInputField" size="8" name="etaddrows_position" value="1" />
18Count: <input type="text" class="foswikiInputField" size="8" name="etaddrows_count" value="5" />
19<input type="submit" name="etsave" id="etsave" value="Add rows" class="foswikiSubmit" />
20</form>
21
22---++ Delete rows from table 1
23<form name="edittable1" action="%SCRIPTURL{"viewauth"}%/Sandbox/EditTablePluginTest2#edittable1" method="post">
24<input type="hidden" name="ettablenr" value="1" />
25Position: <input type="text" class="foswikiInputField" size="8" name="etdeleterows_position" value="1" />
26Count: <input type="text" class="foswikiInputField" size="8" name="etdeleterows_count" value="5" />
27<input type="submit" name="etsave" id="etsave" value="Delete rows" class="foswikiSubmit" />
28</form>
29
30=cut
31
32package Foswiki::Plugins::EditTablePlugin::Core;
33
34use strict;
35use warnings;
36use Assert;
37use Error qw(:try);
38use CGI qw( :all );
39
40use Foswiki::Func;
41use Foswiki::Plugins::EditTablePlugin::Data;
42use Foswiki::Plugins::EditTablePlugin::EditTableData;
43
44my $DEFAULT_FIELD_SIZE           = 16;
45my $PLACEHOLDER_BUTTONROW_TOP    = 'PLACEHOLDER_BUTTONROW_TOP';
46my $PLACEHOLDER_BUTTONROW_BOTTOM = 'PLACEHOLDER_BUTTONROW_BOTTOM';
47my $PLACEHOLDER_SEPARATOR_SEARCH_RESULTS =
48  'PLACEHOLDER_SEPARATOR_SEARCH_RESULTS';
49my $HTML_TAGS =
50qr'var|ul|u|tt|tr|th|td|table|sup|sub|strong|strike|span|small|samp|s|pre|p|ol|li|kbd|ins|img|i|hr|h|font|em|div|dfn|del|code|cite|center|br|blockquote|big|b|address|acronym|abbr|a';
51
52my $prefCHANGEROWS;
53my $prefEDIT_BUTTON;
54my $prefSAVE_BUTTON;
55my $prefQUIET_SAVE_BUTTON;
56my $prefADD_ROW_BUTTON;
57my $prefDELETE_LAST_ROW_BUTTON;
58my $prefCANCEL_BUTTON;
59my $prefMESSAGE_INCLUDED_TOPIC_DOES_NOT_EXIST;
60my $prefQUIETSAVE;
61my $preSp;
62my %params;
63my @format;
64my @formatExpanded;
65my $nrCols;
66my $warningMessage;
67
68my $PATTERN_EDITTABLEPLUGIN =
69  $Foswiki::Plugins::EditTablePlugin::Data::PATTERN_EDITTABLEPLUGIN;
70my $PATTERN_TABLEPLUGIN =
71  $Foswiki::Plugins::EditTablePlugin::Data::PATTERN_TABLEPLUGIN;
72my $PATTERN_EDITCELL               = qr'%EDITCELL\{(.*?)\}%';
73my $PATTERN_TABLE_ROW_FULL         = qr'^(\s*)\|.*\|\s*$';
74my $PATTERN_TABLE_ROW              = qr'^(\s*)\|(.*)';
75my $PATTERN_SPREADSHEETPLUGIN_CALC = qr'%CALC(?:\{(.*?)\})?%';
76my $SPREADSHEETPLUGIN_CALC_SUBSTITUTION =
77  "<span class='editTableCalc'>CALC</span>";
78my $MODE = {
79    READ           => ( 1 << 1 ),
80    EDIT           => ( 1 << 2 ),
81    SAVE           => ( 1 << 3 ),
82    SAVEQUIET      => ( 1 << 4 ),
83    CANCEL         => ( 1 << 5 ),
84    EDITNOTALLOWED => ( 1 << 6 ),
85};
86my %tableMatrix;
87my $query;
88
89=begin TML
90
91Initializes variables.
92
93=cut
94
95sub init {
96    $preSp                      = '';
97    %params                     = ();
98    @format                     = ();
99    @formatExpanded             = ();
100    $prefCHANGEROWS             = undef;
101    $prefEDIT_BUTTON            = undef;
102    $prefSAVE_BUTTON            = undef;
103    $prefQUIET_SAVE_BUTTON      = undef;
104    $prefADD_ROW_BUTTON         = undef;
105    $prefDELETE_LAST_ROW_BUTTON = undef;
106    $prefDELETE_LAST_ROW_BUTTON = undef;
107    $prefQUIETSAVE              = undef;
108    $nrCols                     = undef;
109    $query                      = undef;
110    $warningMessage             = '';
111    %tableMatrix                = ();
112
113    getPreferencesValues();
114}
115
116=begin TML
117
118Init variables again. If called from INCLUDE this is the first time we init
119
120=cut
121
122sub initIncludedTopic {
123    $preSp = '' unless $preSp;
124    getPreferencesValues();
125}
126
127=begin TML
128
129StaticMethod parseTables($text, $topic, $web)
130
131Read and parse table data once for each topic.
132Stores data in hash $tableMatrix{webname}{topicname}.
133Even if we are just viewing table data (not editing), we can deal with text inside edit tables in a special way. For instance by calling handleTmlInTables on the table text.
134
135=cut
136
137sub parseTables {
138    my ( $inText, $inTopic, $inWeb ) = @_;
139
140    # my $text = $_[0]
141
142    return if defined $tableMatrix{$inWeb}{$inTopic};
143
144    my $tableData = Foswiki::Plugins::EditTablePlugin::Data->new();
145    $_[0] = $tableData->parseText($inText);
146
147    _debug("EditTablePlugin::Core::parseTables - after parseText, text=$_[0]");
148
149    $tableMatrix{$inWeb}{$inTopic} = $tableData;
150}
151
152=begin TML
153
154---+++ process( $text, $topic, $web, $includingTopic, $includingWeb )
155
156Called from commonTagsHandler. Pass over to processText in 'no Save' mode.
157
158=cut
159
160sub process {
161
162    # my $text = $_[0]
163    # my $topic = $_[1]
164    # my $web = $_[2]
165    # my $includingTopic = $_[3]
166    # my $includingWeb = $_[4]
167
168    my $mode        = $MODE->{READ};
169    my $saveTableNr = 0;
170    processText( $mode, $saveTableNr, @_ );
171}
172
173=begin TML
174
175---+++ processText( $mode, $saveTableNr, $text, $topic, $web, $includingTopic, $includingWeb )
176
177Process the text line by line.
178When a EditTablePlugin table is encountered, its contents is rendered according to the view:
179   * View mode - default
180   * Edit mode - when an Edit button is clicked, renders the rest of the table in edit mode
181   * Save mode - when called from a Save button: calls processText again, only renders the selected table number, then saves the topic text
182
183=cut
184
185sub processText {
186    my ( $inMode, $inSaveTableNr, $inText, $inTopic, $inWeb, $inIncludingTopic,
187        $inIncludingWeb )
188      = @_;
189
190    my $mode = $inMode;
191    my $doSave = ( $mode & $MODE->{SAVE} ) ? 1 : 0;
192    $query = Foswiki::Func::getCgiQuery();
193
194    # Item1458 ignore all saving unless it happened using POST method.
195    $doSave = 0
196      if ( $query && $query->method() && uc( $query->method() ) ne 'POST' );
197
198    _debug(
199"EditTablePlugin::Core::processText( inSaveTableNr=$inSaveTableNr; inText=$inText\ninTopic=$inTopic\ninWeb=$inWeb\ninIncludingTopic=$inIncludingTopic\ninIncludingWeb=$inIncludingWeb\nmode="
200          . _modeToString($mode) );
201    _debug( "query params=" . Dumper( $query->{param} ) );
202
203    my $topic = $query->param('ettabletopic') || $inTopic;
204    my $web   = $query->param('ettableweb')   || $inWeb;
205
206    my $paramTableNr = $query->param('ettablenr') || 0;
207
208    my $meta;
209    my $topicText = $inText;
210
211    if ($doSave) {
212        ( $meta, $topicText ) = Foswiki::Func::readTopic( $web, $topic );
213
214        # fill the matrix with fresh new table
215        undef $tableMatrix{$web}{$topic};
216        parseTables( $topicText, $topic, $web );
217    }
218    else {
219        parseTables( $inText, $topic, $web );
220    }
221    my $tableData = $tableMatrix{$web}{$topic};
222
223    # ========================================
224    # LOOP THROUGH TABLES
225
226    my $tableNr = 0;    # current EditTable table
227    foreach my Foswiki::Plugins::EditTablePlugin::EditTableData $editTableData (
228        @{ $tableData->{editTableDataList} } )
229    {
230
231        if ($Foswiki::Plugins::EditTablePlugin::debug) {
232            use Data::Dumper;
233            _debug( "EditTablePlugin::Core::processText; editTableData="
234                  . Dumper($editTableData) );
235        }
236
237        my $isEditingTable = 0;
238        my $editTableTag   = $editTableData->{'tagline'};
239
240       # store processed lines of this tableText
241       # the list of lines will be put back into the topic text after processing
242        my @result = ();
243
244        $tableNr++;
245
246        # ========================================
247        # START HANDLE EDITTABLE TAG
248
249        if ( $mode & $MODE->{READ} ) {
250
251            # process the tag contents
252            handleEditTableTag( $web, $topic, $editTableData->{'params'} );
253
254            # remove the original EDITTABLE{} in the tag pre_EDITTABLE{}_post
255            # so we just have pre__post
256            $editTableTag =
257              $editTableData->{'pretag'} . $editTableData->{'posttag'};
258
259            # expand macros in tagline without creating infinite recursion:
260            $editTableTag =~ s/%EDITTABLE\{/%TMP_ETP_STUB_TAG{/;
261            $editTableTag = Foswiki::Func::expandCommonVariables($editTableTag);
262
263            # put tag back
264            $editTableTag =~ s/TMP_ETP_STUB_TAG/EDITTABLE/;
265        }
266
267        # END HANDLE EDITTABLE TAG
268        # ========================================
269
270        # ========================================
271        # START FOOTER AND HEADER ROW COUNT
272
273        ( $editTableData->{headerRowCount}, $editTableData->{footerRowCount} )
274          = getHeaderAndFooterCount($editTableTag);
275
276        _debug(
277"EditTablePlugin::Core::processText; headerRowCount=$editTableData->{headerRowCount}; footerRowCount=$editTableData->{footerRowCount}; tableText="
278              . join( "\n", @{ $editTableData->{'lines'} } ) );
279
280        # END FOOTER AND HEADER ROW COUNT
281        # ========================================
282
283        # ========================================
284        # START HANDLE TABLE CHANGE PARAMETERS
285
286        my $tableChanges =
287          Foswiki::Plugins::EditTablePlugin::EditTableData::createTableChangesMap(
288            $query->multi_param('ettablechanges') )
289          ; # a mapping of rows and their changed state; keys are row numbers (starting with 0), values are row states: 0 (not changed), 1 (row to be added), -1 (row to be deleted); the map is created using the param 'ettablechanges'; the map is created and updated in EDIT mode, and used in SAVE mode.
290
291        my $tableStats = $editTableData->getTableStatistics($tableChanges);
292
293        if ( ( $mode & $MODE->{READ} ) || ( $tableNr == $inSaveTableNr ) ) {
294
295            my $allowedToEdit = 0;
296
297            if ( $tableNr == $inSaveTableNr ) {
298                $mode =
299                  handleTableChangeParams( $mode, $editTableData, $tableStats,
300                    $tableChanges, $web, $topic );
301                $tableStats = $editTableData->getTableStatistics($tableChanges);
302            }
303
304            if (
305                ( $paramTableNr == $tableNr )
306                && (  $web . '.'
307                    . $topic eq
308"$Foswiki::Plugins::EditTablePlugin::web.$Foswiki::Plugins::EditTablePlugin::topic"
309                )
310              )
311            {
312                $isEditingTable = 1;
313
314                # handle button actions
315                if ( $mode & $MODE->{READ} ) {
316
317                    $mode =
318                      handleButtonActions( $mode, $editTableData, $tableStats,
319                        $tableChanges, $web, $topic );
320
321                    if (   ( $mode & $MODE->{SAVE} )
322                        || ( $mode & $MODE->{SAVEQUIET} ) )
323                    {
324                        return processText( $mode, $tableNr, $inText, $topic,
325                            $web, $inIncludingTopic, $inIncludingWeb );
326                    }
327                    elsif ( $mode & $MODE->{CANCEL} ) {
328                        return;    # in case browser does not redirect
329                    }
330                    elsif ( $mode & $MODE->{EDITNOTALLOWED} ) {
331                        return;
332                    }
333                }
334            }
335        }   # if ( ( $mode & $MODE->{READ} ) || ( $tableNr == $inSaveTableNr ) )
336
337        # END HANDLE TABLE CHANGE PARAMETERS
338        # ========================================
339
340        # ========================================
341        # START FORM TOP
342
343        my $doEdit = $isEditingTable ? 1 : 0;
344
345        if ( ( $mode & $MODE->{READ} ) ) {
346            my $tableStart = handleTableStart( $web, $topic, $inIncludingWeb,
347                $inIncludingTopic, $tableNr, $doEdit );
348            push( @result, $tableStart );
349        }
350
351        # END FORM TOP
352        # ========================================
353
354        # ========================================
355        # START PROCESSING ROWS
356
357        if ($isEditingTable) {
358            ( my $processedTableData, $tableChanges ) =
359              processTableData( $tableNr, $editTableData, $tableChanges,
360                $doEdit, $doSave, $web, $topic );
361            push( @result, @{$processedTableData} );
362        }
363        else {
364
365            my $lines = $editTableData->{'lines'};
366
367            # render the row: EDITCELL and format tokens
368            my $rowNr = 0;
369            for ( @{$lines} ) {
370                my $isNewRow = 0;
371s/$PATTERN_TABLE_ROW/handleTableRow( $1, $2, $tableNr, $isNewRow, $rowNr++, $doEdit, $doSave, $web, $topic )/e;
372            }
373            @{$lines} = map { $_ .= "\n" } @{$lines};
374            handleTmlInTables($lines) if !$doSave;
375
376            push( @result, @{$lines} );
377        }
378
379        # END PROCESSING ROWS
380        # ========================================
381
382        # ========================================
383        # START PUT PROCESSED TABLE BACK IN TEXT
384
385        my $resultText = join( "", @result );
386
387        # We do not want TablePlugin to sort the table we are editing
388        # So we use the special setting disableallsort which is added
389        # to TABLE for exactly this purpose. Please do not remove this
390        # feature again.
391        if (  !$doSave
392            && $isEditingTable )
393        {
394            _debug("editTableTag before changing TABLE tag=$editTableTag");
395            my $TABLE_EDIT_TAGS = 'disableallsort="on" databg="#fff"';
396            if ( $editTableTag !~ /%TABLE\{.*?\}%/ ) {
397
398                # no TABLE tag at all
399                $editTableTag .= "%TABLE{$TABLE_EDIT_TAGS}%";
400            }
401            elsif ( $editTableTag !~ /%TABLE\{.*?$TABLE_EDIT_TAGS.*?\}%/ ) {
402                $editTableTag =~ s/(%TABLE\{.*?)(\}%)/$1 $TABLE_EDIT_TAGS$2/;
403            }
404            _debug("editTableTag after changing TABLE tag=$editTableTag");
405        }
406
407        # The Data::parseText merges a TABLE and EDITTABLE to one line
408        # We split it again to make editing easier for the user
409        # If the two were originally one line - they now become two unless
410        # there was white space between them
411        $editTableTag =~ s/(%EDITTABLE\{.*?\}%)(%TABLE\{.*?\}%)/$1\n$2/;
412        $editTableTag =~ s/(%TABLE\{.*?\}%)(%EDITTABLE\{.*?\}%)/$1\n$2/;
413
414        $resultText = "$editTableTag\n$resultText";
415
416        # END PUT PROCESSED TABLE BACK IN TEXT
417        # ========================================
418
419        # ========================================
420        # START FORM BOTTOM
421
422        if ( ( $mode & $MODE->{READ} ) ) {
423            my $tableEnd = handleTableEnd(
424                $doEdit, $tableChanges,
425                $tableStats->{headerRowCount},
426                $tableStats->{footerRowCount}
427            );
428            $resultText .= $tableEnd;
429        }
430        chomp $resultText;    # remove spurious newline at end
431
432        # END FORM BOTTOM
433        # ========================================
434
435        # ========================================
436        # START BUTTON ROWS
437
438        # button row at top or bottom
439        if ( ( $mode & $MODE->{READ} ) ) {
440            my $pos = $params{'buttonrow'} || 'bottom';
441            my $buttonRow =
442              createButtonRow( $web, $topic, $inIncludingWeb, $inIncludingTopic,
443                $doEdit );
444            if ( $pos eq 'top' ) {
445                $resultText =~ s/$PLACEHOLDER_BUTTONROW_BOTTOM//g;    # remove
446                $resultText =~ s/$PLACEHOLDER_BUTTONROW_TOP/$buttonRow/g;
447            }
448            else {
449                $resultText =~ s/$PLACEHOLDER_BUTTONROW_TOP//g;       # remove
450                $resultText =~ s/$PLACEHOLDER_BUTTONROW_BOTTOM/$buttonRow/g;
451            }
452        }
453
454        # render variables (only in view mode)
455        $resultText = Foswiki::Func::expandCommonVariables($resultText)
456          if ( $mode & $MODE->{READ} );
457
458        _debug("After parsing, resultText=$resultText");
459        _debug("After parsing, tableNr=$tableNr");
460
461        $topicText =~ s/<!--%EDITTABLESTUB\{$tableNr\}%-->/$resultText/g;
462
463        # END BUTTON ROWS
464        # ========================================
465
466    }    # foreach
467
468    # ========================================
469    # START SAVE
470
471    if ($doSave) {
472        my $url = Foswiki::Func::getViewUrl( $web, $topic );
473        try {
474            Foswiki::Func::saveTopic( $web, $topic, $meta, $topicText,
475                { dontlog => ( $mode & $MODE->{SAVEQUIET} ) } );
476        }
477        catch Error::Simple with {
478            my $e = shift;
479            $url =
480              Foswiki::Func::getOopsUrl( $web, $topic, 'oopssaveerr',
481                "Save failed: " . $e->{-text} );
482        };
483        Foswiki::Func::setTopicEditLock( $web, $topic, 0 );    # unlock Topic
484        $url .= "#edittable$inSaveTableNr";
485        Foswiki::Func::redirectCgiQuery( $query, $url );
486        return;
487    }
488
489    # END SAVE
490    # ========================================
491
492    # update the text
493    $_[2] = $topicText;
494
495    _debug("After parsing, topic text=$_[2]");
496}
497
498=begin TML
499
500NOT FULLY IMPLEMENTED YET
501
502Change table by means of parameters:
503   1 Adding rows:
504      * param etaddrows_position
505      * param etaddrows_count
506   1 Deleting rows
507      * param etdeleterows_position
508      * param etdeleterows_count
509
510TODO:
511   * addRows: existing rows need to shift down
512   * deleteRows: check limit start and end of table
513   * create unit test
514   * write documentation
515
516=cut
517
518sub handleTableChangeParams {
519    my ( $inMode, $inEditTableData, $inTableStats, $inTableChanges, $inWeb,
520        $inTopic )
521      = @_;
522
523    return $inMode;    # until fully implemented
524
525    # add rows
526    {
527        my $position = $query->param('etaddrows_position');
528        my $count    = $query->param('etaddrows_count');
529        if ( defined $position && defined $count ) {
530            if ( !doEnableEdit( $inWeb, $inTopic, 1 ) ) {
531                my $mode = $MODE->{EDITNOTALLOWED};
532                return $mode;
533            }
534            addRows( $inTableStats, $inTableChanges, $position, $count );
535        }
536    }
537
538    # delete rows
539    {
540        my $position = $query->param('etdeleterows_position');
541        my $count    = $query->param('etdeleterows_count');
542        if ( defined $position && defined $count ) {
543            if ( !doEnableEdit( $inWeb, $inTopic, 1 ) ) {
544                my $mode = $MODE->{EDITNOTALLOWED};
545                return $mode;
546            }
547            deleteRows( $inTableStats, $inTableChanges, $position, $count );
548        }
549    }
550}
551
552=begin TML
553
554StaticMethod handleButtonActions( $mode, $editTableData, $tableStats, $tableChanges, $web, $topic ) -> $mode
555
556Handles button interaction; for each state updates the $mode to a value of $MODE.
557
558=cut
559
560sub handleButtonActions {
561    my ( $inMode, $inEditTableData, $inTableStats, $inTableChanges, $inWeb,
562        $inTopic )
563      = @_;
564
565    my $mode = $inMode;
566
567    if ( $query->param('etcancel') ) {
568
569        # [Cancel] button pressed
570        doCancelEdit( $inWeb, $inTopic );
571        $mode = $MODE->{CANCEL};
572        return $mode;
573    }
574
575    # else
576    if ( !doEnableEdit( $inWeb, $inTopic, 1 ) ) {
577        $mode = $MODE->{EDITNOTALLOWED};
578        return $mode;
579    }
580
581    # else
582    if ( $query->param('etsave') ) {
583
584        # [Save table] button pressed
585        $mode = $MODE->{SAVE};
586    }
587    elsif ( $query->param('etqsave') ) {
588
589        # [Quiet save] button pressed
590        $mode = $MODE->{SAVE} | $MODE->{SAVEQUIET};
591    }
592    elsif ( $query->param('etaddrow') ) {
593
594        # [Add row] button pressed
595        my $rowNum =
596          $inTableStats->{rowCount} - $inTableStats->{footerRowCount} + 1;
597        addRows( $inTableStats, $inTableChanges, $rowNum, 1 );
598    }
599    elsif ( $query->param('etdelrow') ) {
600
601        # [Delete row] button pressed
602        my $rowNum =
603          $inTableStats->{rowCount} - $inTableStats->{footerRowCount};
604        deleteRows( $inTableStats, $inTableChanges, $rowNum, 1 );
605    }
606    elsif ( $query->param('etedit') ) {
607
608        # [Edit table] button pressed
609        # just continue
610    }
611
612    return $mode;
613}
614
615=begin TML
616
617StaticMethod addRows( $tableStats, $tableChanges, $position, $count )
618
619Adds one or more rows.
620
621=cut
622
623sub addRows {
624    my ( $inTableStats, $inTableChanges, $inPosition, $inCount ) = @_;
625
626    my $row = $inPosition;
627
628    _debug("ADD ROW:$row");
629
630    while ( $inCount-- ) {
631
632# if we have set a row to 'delete' before, reset it to 2 first; otherwise set an existing row to 'add'
633        $inTableChanges->{$row} =
634          ( defined $inTableChanges->{$row} && $inTableChanges->{$row} == -1 )
635          ? 2
636          : 1;
637        $row++;
638    }
639}
640
641=begin TML
642
643StaticMethod deleteRows( $tableStats, $tableChanges, $position, $count )
644
645Deletes one or more rows.
646
647=cut
648
649sub deleteRows {
650    my ( $inTableStats, $inTableChanges, $inPosition, $inCount ) = @_;
651
652    my $row = $inPosition;
653
654    # run backwards in rows to see which row has not been deleted yet
655    while ($row) {
656        last
657          if ( !defined $inTableChanges->{$row}
658            || $inTableChanges->{$row} != -1 );
659        $row--;
660    }
661
662    while ( $inCount-- ) {
663
664# if we have set a row to 'add' before, reset it to 0 first; otherwise set an existing row to 'delete'
665        $inTableChanges->{$row} =
666          ( defined $inTableChanges->{$row} && $inTableChanges->{$row} == 1 )
667          ? 0
668          : -1;
669        $row--;
670    }
671}
672
673=begin TML
674
675StaticMethod processTableData( $tableNr, $editTableData, $tableChanges, $doEdit, $doSave, $web, $topic ) -> (\@processedText, \%tableChanges)
676
677=cut
678
679sub processTableData {
680    my ( $inTableNr, $inEditTableData, $inTableChanges, $inDoEdit, $inDoSave,
681        $inWeb, $inTopic )
682      = @_;
683
684    my @rows         = ();
685    my $tableChanges = $inTableChanges;
686    my @result       = ();
687    my $tableStats   = $inEditTableData->getTableStatistics($tableChanges);
688
689    _debug( "EditTablePlugin::processTableData - inEditTableData at start="
690          . Dumper($inEditTableData) );
691    _debug( "EditTablePlugin::processTableData - tableStats at start="
692          . Dumper($tableStats) );
693    _debug( "EditTablePlugin::processTableData - tableChanges at start="
694          . Dumper($tableChanges) );
695
696    my @headerRows = ();
697    my @footerRows = ();
698    my @bodyRows   = ();
699
700    # ========================================
701    my $bodyRowCount =
702      $inEditTableData->{'rowCount'} -
703      $inEditTableData->{'footerRowCount'} -
704      $inEditTableData->{'headerRowCount'};
705    $bodyRowCount = 0 if $bodyRowCount < 0;
706
707    # map 'real' rows to types
708    # at this point we still use the order as used in the topic
709    # so the footer will be in the last rows
710    # we are not yet using the updated state of $tableStats
711
712    my @headerRowNums = ();
713    my @footerRowNums = ();
714    my @bodyRowNums   = ();
715    my $rowNr         = 0;
716
717    for ( @{ $inEditTableData->{'lines'} } ) {
718
719        $rowNr++;
720        $tableChanges->{$rowNr} ||= 0;
721
722        if ( $rowNr <= $inEditTableData->{'headerRowCount'} ) {
723
724            push @headerRowNums, $rowNr if ( $tableChanges->{$rowNr} != -1 );
725        }
726        elsif ( ( $rowNr > $inEditTableData->{'headerRowCount'} )
727            && (
728                $rowNr <= $inEditTableData->{'headerRowCount'} + $bodyRowCount )
729          )
730        {
731            push @bodyRowNums, $rowNr if ( $tableChanges->{$rowNr} != -1 );
732        }
733        else {
734            push @footerRowNums, $rowNr;
735        }
736    }
737
738    # END GET THE ROW TYPE
739    # ========================================
740
741    # ========================================
742    # START RENDER CURRENT ROWS
743
744    foreach my $rowNr (@headerRowNums) {
745        next
746          if ( $inTableChanges
747            && defined $inTableChanges->{$rowNr}
748            && $inTableChanges->{$rowNr} == -1 );
749        local $_ = $inEditTableData->{'lines'}->[ $rowNr - 1 ];
750
751        my $rowId    = $rowNr;
752        my $isNewRow = 0;
753
754# a header row will not be edited, so do not render table row with handleTableRow
755
756        # _handleSpreadsheetFormula($_);
757        _debug("RENDER ROW: HEADER:rowNr=$rowNr; id=$rowId=$_");
758        push( @headerRows, $_ );
759    }
760
761    foreach my $rowNr (@footerRowNums) {
762        next
763          if ( $inTableChanges
764            && defined $inTableChanges->{$rowNr}
765            && $inTableChanges->{$rowNr} == -1 );
766        local $_ = $inEditTableData->{'lines'}->[ $rowNr - 1 ];
767
768        my $rowId    = $rowNr;
769        my $isNewRow = 0;
770
771# a footer row will not be edited, so do not render table row with handleTableRow
772
773        # _handleSpreadsheetFormula($_);
774        _debug("RENDER ROW: FOOTER:rowNr=$rowNr; id=$rowId=$_");
775        push( @footerRows, $_ );
776    }
777
778    foreach my $rowNr (@bodyRowNums) {
779        next
780          if ( $inTableChanges
781            && defined $inTableChanges->{$rowNr}
782            && $inTableChanges->{$rowNr} == -1 );
783        local $_ = $inEditTableData->{'lines'}->[ $rowNr - 1 ];
784
785        my $rowId    = @headerRows + scalar @bodyRows + 1;
786        my $isNewRow = 0;
787        if ( $tableChanges->{$rowId} && $tableChanges->{$rowId} == 2 ) {
788            $isNewRow = 1;
789            $tableChanges->{$rowId} = 0;
790        }
791
792s/$PATTERN_TABLE_ROW/handleTableRow( $1, $2, $inTableNr, $isNewRow, $rowId, $inDoEdit, $inDoSave, $inWeb, $inTopic )/eo;
793
794        _debug("RENDER ROW: BODY:rowNr=$rowNr; id=$rowId=$_");
795        push( @bodyRows, $_ );
796    }
797
798    # END RENDER CURRENT ROWS
799    # ========================================
800
801    # ========================================
802    # START ADDING ROWS
803
804    # START ADDING HEADER ROWS
805
806    # add a header row if there is none,
807    # but only if there are no body rows yet
808    # (and not when a row has just been deleted)
809
810    if ( !( scalar @bodyRows ) ) {
811
812        my $headerRows = $query->param('etheaderrows') || 0;
813        if ( $headerRows > scalar @headerRows ) {
814            my $rowNr        = scalar @headerRows;
815            my $newHeaderRow = $headerRows - @headerRows;
816            while ( $newHeaderRow-- ) {
817
818                my $rowId =
819                  scalar @headerRows +
820                  scalar @footerRows +
821                  scalar @bodyRows + 1;
822                next
823                  if ( $tableChanges->{$rowId} == -1
824                    || $tableChanges->{$rowId} == 2 );
825
826                $tableChanges->{$rowId} = 1;    # store the new row
827                my $isNewRow = 1;
828                my $newRow   = handleTableRow(
829                    '',     '',        $inTableNr, $isNewRow,
830                    $rowId, $inDoEdit, $inDoSave,  $inWeb,
831                    $inTopic
832                );
833                push @headerRows, $newRow;
834            }
835        }
836
837        # to start with table editing right away, add a minimum of 1 body row
838        if ( !$tableStats->{bodyRowCount} ) {
839
840            # put row at bottom
841            my $rowId =
842              scalar @headerRows + scalar @footerRows + scalar @bodyRows + 1;
843            $tableChanges->{$rowId} ||= 0;
844            if (
845                !(
846                       $tableChanges->{$rowId} == -1
847                    || $tableChanges->{$rowId} == 2
848                )
849              )
850            {
851                $tableChanges->{$rowId} = 1;    # store the new row
852                my $isNewRow = 0;
853                my $newRow   = handleTableRow(
854                    '',     '',        $inTableNr, $isNewRow,
855                    $rowId, $inDoEdit, $inDoSave,  $inWeb,
856                    $inTopic
857                );
858                push @bodyRows, $newRow;
859            }
860        }
861
862        # update table stats
863        $tableStats = $inEditTableData->getTableStatistics($tableChanges);
864    }
865
866    # END ADDING HEADER ROWS
867
868    # START ADDING BODY ROWS
869
870    my $bodyRows = $tableStats->{bodyRowCount};
871
872    if ( $bodyRows > scalar @bodyRows ) {
873
874        my $rowNr = scalar @headerRows + scalar @footerRows + scalar @bodyRows;
875        my $newBodyRow = $bodyRows - @bodyRows;
876
877        _debug("ADD ROW: BODY: number=$newBodyRow");
878
879        while ( $newBodyRow-- ) {
880            $rowNr++;
881
882            next
883              if (
884                (
885                    defined $inTableChanges->{$rowNr}
886                    && $inTableChanges->{$rowNr} == -1
887                )
888                || ( defined $inTableChanges->{$rowNr}
889                    && $inTableChanges->{$rowNr} == 2 )
890              );
891
892            my $rowId = scalar @headerRows + scalar @bodyRows + 1;
893
894            _debug("ADD ROW: BODY: rowId=$rowId");
895
896            my $isNewRow = 0
897              ; # otherwise values entered in new rows are not preserved when adding yet more rows
898
899            my $newRow = handleTableRow(
900                '',     '',        $inTableNr, $isNewRow,
901                $rowId, $inDoEdit, $inDoSave,  $inWeb,
902                $inTopic
903            );
904
905            _debug("ADD ROW: BODY: newRow=$newRow");
906
907            push @bodyRows, $newRow;
908        }
909
910        # update table stats
911        $tableStats = $inEditTableData->getTableStatistics($tableChanges);
912    }
913
914    # END ADDING BODY ROWS
915
916    # END ADDING ROWS
917    # ========================================
918
919    _debug( "EditTablePlugin::processTableData - tableChanges at end="
920          . Dumper($tableChanges) );
921    _debug( "EditTablePlugin::processTableData - tableStats at end="
922          . Dumper($tableStats) );
923
924    _debug(
925"EditTablePlugin::processTableData - headerRows=\n---------------------\n"
926          . Dumper(@headerRows) );
927    _debug(
928        "EditTablePlugin::processTableData - bodyRows=\n---------------------\n"
929          . Dumper(@bodyRows) );
930    _debug(
931"EditTablePlugin::processTableData - footerRows=\n---------------------\n"
932          . Dumper(@footerRows) );
933
934    my @combinedRows = ( @headerRows, @bodyRows, @footerRows );
935
936    _debug(
937"EditTablePlugin::processTableData - combinedRows=\n---------------------\n"
938          . Dumper(@combinedRows) );
939
940    push( @result, @combinedRows );
941
942    @result = map { $_ .= "\n" } @result;
943
944    return ( \@result, $tableChanges );
945}
946
947=begin TML
948
949StaticMethod getPreferencesValues()
950
951Read preferences from plugin topic of preferences.
952
953=cut
954
955sub getPreferencesValues {
956
957    my $pluginName = $Foswiki::Plugins::EditTablePlugin::pluginName;
958
959    $prefCHANGEROWS =
960      Foswiki::Func::getPreferencesValue("\U$pluginName\E_CHANGEROWS") || 'on';
961
962    $prefQUIETSAVE =
963      Foswiki::Func::getPreferencesValue("\U$pluginName\E_QUIETSAVE") || 'on';
964
965    $prefEDIT_BUTTON =
966      Foswiki::Func::getPreferencesValue("\U$pluginName\E_EDIT_BUTTON")
967      || '%MAKETEXT{"Edit this table"}%, %ATTACHURL%/edittable.gif';
968
969    $prefSAVE_BUTTON =
970      Foswiki::Func::getPreferencesValue("\U$pluginName\E_SAVE_BUTTON")
971      || '%MAKETEXT{"Save table"}%';
972
973    $prefQUIET_SAVE_BUTTON =
974      Foswiki::Func::getPreferencesValue("\U$pluginName\E_QUIET_SAVE_BUTTON")
975      || '%MAKETEXT{"Quiet save"}%';
976
977    $prefADD_ROW_BUTTON =
978      Foswiki::Func::getPreferencesValue("\U$pluginName\E_ADD_ROW_BUTTON")
979      || '%MAKETEXT{"Add row"}%';
980
981    $prefDELETE_LAST_ROW_BUTTON = Foswiki::Func::getPreferencesValue(
982        "\U$pluginName\E_DELETE_LAST_ROW_BUTTON")
983      || '%MAKETEXT{"Delete last row"}%';
984
985    $prefCANCEL_BUTTON =
986      Foswiki::Func::getPreferencesValue("\U$pluginName\E_CANCEL_BUTTON")
987      || '%MAKETEXT{"Cancel"}%';
988
989    $prefMESSAGE_INCLUDED_TOPIC_DOES_NOT_EXIST =
990      Foswiki::Func::getPreferencesValue(
991        "\U$pluginName\E_INCLUDED_TOPIC_DOES_NOT_EXIST")
992      || '<span class="foswikiAlert">%MAKETEXT{"Warning: \'include\' topic does not exist!"}%</span>';
993}
994
995=begin TML
996
997StaticMethod extractParams( $arguments, \%params )
998
999=cut
1000
1001sub extractParams {
1002    my ( $inArguments, $inParams ) = @_;
1003
1004    my $tmp;
1005
1006    $tmp = Foswiki::Func::extractNameValuePair( $inArguments, 'header' );
1007    $$inParams{'header'} = $tmp if ($tmp);
1008
1009    $tmp = Foswiki::Func::extractNameValuePair( $inArguments, 'footer' );
1010    $$inParams{'footer'} = $tmp if ($tmp);
1011
1012    $tmp = Foswiki::Func::extractNameValuePair( $inArguments, 'headerislabel' );
1013    $$inParams{'headerislabel'} = $tmp if ($tmp);
1014
1015    $tmp = Foswiki::Func::extractNameValuePair( $inArguments, 'format' );
1016    $tmp =~ s/^\s*\|*\s*//;
1017    $tmp =~ s/\s*\|*\s*$//;
1018    $$inParams{'format'} = $tmp if ($tmp);
1019
1020    $tmp = Foswiki::Func::extractNameValuePair( $inArguments, 'changerows' );
1021    $$inParams{'changerows'} = $tmp if ($tmp);
1022
1023    $tmp = Foswiki::Func::extractNameValuePair( $inArguments, 'quietsave' );
1024    $$inParams{'quietsave'} = $tmp if ($tmp);
1025
1026    $tmp = Foswiki::Func::extractNameValuePair( $inArguments, 'helptopic' );
1027    $$inParams{'helptopic'} = $tmp if ($tmp);
1028
1029    $tmp = Foswiki::Func::extractNameValuePair( $inArguments, 'editbutton' );
1030    $$inParams{'editbutton'} = $tmp if ($tmp);
1031
1032    $tmp = Foswiki::Func::extractNameValuePair( $inArguments,
1033        'javascriptinterface' );
1034    $$inParams{'javascriptinterface'} = $tmp if ($tmp);
1035
1036    $tmp = Foswiki::Func::extractNameValuePair( $inArguments, 'buttonrow' );
1037    $$inParams{'buttonrow'} = $tmp if ($tmp);
1038}
1039
1040=begin TML
1041
1042=cut
1043
1044sub parseFormat {
1045    my ( $theFormat, $inTopic, $inWeb, $doExpand ) = @_;
1046
1047    $theFormat =~ s/\$nop(\(\))?//gs;         # remove filler
1048    $theFormat =~ s/\$quot(\(\))?/\"/gs;      # expand double quote
1049    $theFormat =~ s/\$percnt(\(\))?/\%/gs;    # expand percent
1050    $theFormat =~ s/\$dollar(\(\))?/\$/gs;    # expand dollar
1051
1052    if ($doExpand) {
1053
1054        # expanded form to be able to use %-vars in format
1055        $theFormat =~ s/<nop>//gs;
1056        $theFormat =
1057          Foswiki::Func::expandCommonVariables( $theFormat, $inTopic, $inWeb );
1058    }
1059
1060    my @aFormat = split( /\s*\|\s*/, $theFormat );
1061    $aFormat[0] = "text,$DEFAULT_FIELD_SIZE" unless @aFormat;
1062
1063    return @aFormat;
1064}
1065
1066=begin TML
1067
1068=cut
1069
1070sub handleEditTableTag {
1071    my ( $inWeb, $inTopic, $inArguments ) = @_;
1072
1073    %params = (
1074        'header'              => '',
1075        'footer'              => '',
1076        'headerislabel'       => "1",
1077        'format'              => '',
1078        'changerows'          => $prefCHANGEROWS,
1079        'quietsave'           => $prefQUIETSAVE,
1080        'helptopic'           => '',
1081        'editbutton'          => '',
1082        'javascriptinterface' => '',
1083        'buttonrow'           => '',
1084    );
1085    $warningMessage = '';
1086
1087    # include topic to read definitions
1088    my $iTopic = Foswiki::Func::extractNameValuePair( $inArguments, 'include' );
1089    if ($iTopic) {
1090        ( $inWeb, $iTopic ) =
1091          Foswiki::Func::normalizeWebTopicName( $inWeb, $iTopic );
1092
1093        unless ( Foswiki::Func::topicExists( $inWeb, $iTopic ) ) {
1094            $warningMessage = $prefMESSAGE_INCLUDED_TOPIC_DOES_NOT_EXIST;
1095        }
1096        else {
1097
1098            my $text = Foswiki::Func::readTopicText( $inWeb, $iTopic );
1099            $text =~ /$PATTERN_EDITTABLEPLUGIN/s;
1100            if ($2) {
1101                my $args = $2;
1102                if (   $inWeb ne $Foswiki::Plugins::EditTablePlugin::web
1103                    || $iTopic ne $Foswiki::Plugins::EditTablePlugin::topic )
1104                {
1105
1106                    # expand common vars, unless oneself to prevent recursion
1107                    $args =
1108                      Foswiki::Func::expandCommonVariables( $args, $iTopic,
1109                        $inWeb );
1110                }
1111                extractParams( $args, \%params );
1112            }
1113        }
1114    }
1115
1116    # We allow expansion of macros in the EDITTABLE arguments so one can
1117    # set a macro that defines the arguments
1118
1119    my $arguments =
1120      Foswiki::Func::expandCommonVariables( $inArguments, $inTopic, $inWeb );
1121
1122    extractParams( $arguments, \%params );
1123
1124    # FIXME: should use Foswiki::Func::extractParameters
1125    $params{'header'} = '' if ( $params{header} =~ /^(off|no)$/i );
1126    $params{'header'} =~ s/^\s*\|//;
1127    $params{'header'} =~ s/\|\s*$//;
1128    $params{'headerislabel'} = ''
1129      if ( $params{headerislabel} =~ /^(off|no)$/i );
1130    $params{'footer'} = '' if ( $params{footer} =~ /^(off|no)$/i );
1131    $params{'footer'} =~ s/^\s*\|//;
1132    $params{'footer'} =~ s/\|\s*$//;
1133
1134    $params{'changerows'} = '' if ( $params{changerows} =~ /^(off|no)$/i );
1135    $params{'quietsave'}  = '' if ( $params{quietsave}  =~ /^(off|no)$/i );
1136    $params{'javascriptinterface'} = 'off'
1137      if ( $params{javascriptinterface} =~ /^(off|no)$/i );
1138
1139    @format         = parseFormat( $params{format}, $inTopic, $inWeb, 0 );
1140    @formatExpanded = parseFormat( $params{format}, $inTopic, $inWeb, 1 );
1141    $nrCols         = scalar @format;
1142}
1143
1144=begin TML
1145
1146Creates the HTML for the start of the table.
1147
1148=cut
1149
1150sub handleTableStart {
1151    my ( $inWeb, $inTopic, $inIncludingWeb, $inIncludingTopic, $theTableNr,
1152        $doEdit )
1153      = @_;
1154
1155    if ($doEdit) {
1156        require Foswiki::Contrib::JSCalendarContrib;
1157        unless ($@) {
1158            Foswiki::Contrib::JSCalendarContrib::addHEAD('foswiki');
1159        }
1160    }
1161
1162    my $viewUrl = Foswiki::Func::getScriptUrl( $inWeb, $inTopic, 'viewauth' )
1163      . "\#edittable$theTableNr";
1164
1165    my $text = '';
1166    $text .= "$preSp<noautolink>\n" if $doEdit;
1167    $text .= "$preSp<a name=\"edittable$theTableNr\"></a>\n"
1168      if ( "$inWeb.$inTopic" eq "$inIncludingWeb.$inIncludingTopic" );
1169    my $cssClass = 'editTable';
1170    if ($doEdit) {
1171        $cssClass .= ' editTableEdit';
1172    }
1173    $text .= "<div class=\"" . $cssClass . "\">\n";
1174    my $formName = "edittable$theTableNr";
1175    $formName .= "\_$inIncludingWeb\_$inIncludingTopic"
1176      if ( "$inWeb\_$inTopic" ne "$inIncludingWeb\_$inIncludingTopic" );
1177    $text .=
1178      "$preSp<form name=\"$formName\" action=\"$viewUrl\" method=\"post\">\n";
1179
1180    $text .= $PLACEHOLDER_BUTTONROW_TOP;
1181
1182    $text .= hiddenField( $preSp, 'ettablenr', $theTableNr, "\n" );
1183    $text .= hiddenField( $preSp, 'etedit',    'on',        "\n" );
1184
1185    return $text;
1186}
1187
1188=begin TML
1189
1190=cut
1191
1192sub handleTableEnd {
1193    my ( $inDoEdit, $inTableChanges, $inHeaderRowCount, $inFooterRowCount ) =
1194      @_;
1195
1196    my $text = '';
1197
1198    $text .= hiddenField(
1199        $preSp,
1200        'ettablechanges',
1201        Foswiki::Plugins::EditTablePlugin::EditTableData::tableChangesMapToParamString(
1202            $inTableChanges),
1203        "\n"
1204    );
1205    $text .= hiddenField( $preSp, 'etheaderrows', $inHeaderRowCount, "\n" );
1206    $text .= hiddenField( $preSp, 'etfooterrows', $inFooterRowCount, "\n" );
1207
1208    $text .= $PLACEHOLDER_BUTTONROW_BOTTOM;
1209
1210    $text .= "$preSp</form>\n";
1211    $text .= "</div><!-- /editTable -->";
1212    $text .= "</noautolink>" if $inDoEdit;
1213
1214    return $text;
1215}
1216
1217=begin TML
1218
1219=cut
1220
1221sub hiddenField {
1222    my ( $inPrefix, $inName, $inValue, $inSuffix ) = @_;
1223
1224    my $prefix = defined $inPrefix ? $inPrefix : '';
1225    my $suffix = defined $inSuffix ? $inSuffix : '';
1226
1227    # Somehow this does not work at all:
1228    # return $prefix
1229    #   . CGI::hidden(
1230    #     -name  => $name,
1231    #     -value => $value
1232    #   ) . $suffix;
1233
1234    return
1235"$prefix<input type=\"hidden\" name=\"$inName\" value=\"$inValue\" />$suffix";
1236}
1237
1238=begin TML
1239
1240=cut
1241
1242sub createButtonRow {
1243    my ( $inWeb, $inTopic, $inIncludingWeb, $inIncludingTopic, $doEdit ) = @_;
1244
1245    my $text = '';
1246    if ( $doEdit
1247        && ( "$inWeb.$inTopic" eq "$inIncludingWeb.$inIncludingTopic" ) )
1248    {
1249
1250        # Edit mode
1251        $text .=
1252"$preSp<input type=\"submit\" name=\"etsave\" id=\"etsave\" value=\"$prefSAVE_BUTTON\" class=\"foswikiSubmit\" />\n";
1253        if ( $params{'quietsave'} ) {
1254            $text .=
1255"$preSp<input type=\"submit\" name=\"etqsave\" id=\"etqsave\" value=\"$prefQUIET_SAVE_BUTTON\" class=\"foswikiButton\" />\n";
1256        }
1257        if ( $params{'changerows'} ) {
1258            $text .=
1259"$preSp<input type=\"submit\" name=\"etaddrow\" id=\"etaddrow\" value=\"$prefADD_ROW_BUTTON\" class=\"foswikiButton\" />\n";
1260            $text .=
1261"$preSp<input type=\"submit\" name=\"etdelrow\" id=\"etdelrow\" value=\"$prefDELETE_LAST_ROW_BUTTON\" class=\"foswikiButton\" />\n"
1262              unless ( $params{'changerows'} =~ /^add$/i );
1263        }
1264        $text .=
1265"$preSp<input type=\"submit\" name=\"etcancel\" id=\"etcancel\" value=\"$prefCANCEL_BUTTON\" class=\"foswikiButtonCancel\" />\n";
1266
1267        if ( $params{'helptopic'} ) {
1268
1269            # read help topic and show below the table
1270            if ( $params{'helptopic'} =~ /^([^\.]+)\.(.*)$/ ) {
1271                $inWeb = $1;
1272                $params{'helptopic'} = $2;
1273            }
1274            my $helpText =
1275              Foswiki::Func::readTopicText( $inWeb, $params{'helptopic'} );
1276
1277            #Strip out the meta data so it won't be displayed.
1278            $helpText =~ s/%META:[A-Za-z0-9]+{.*?}%//g;
1279            if ($helpText) {
1280                $helpText =~ s/.*?%STARTINCLUDE%//s;
1281                $helpText =~ s/%STOPINCLUDE%.*//s;
1282                $text .= $helpText;
1283            }
1284        }
1285
1286        # table specific script
1287        my $tableNr = $query->param('ettablenr');
1288        &Foswiki::Plugins::EditTablePlugin::addEditModeHeadersToHead( $tableNr,
1289            $params{'javascriptinterface'} );
1290        &Foswiki::Plugins::EditTablePlugin::addJavaScriptInterfaceDisabledToHead
1291          ($tableNr)
1292          if ( $params{'javascriptinterface'} eq 'off' );
1293        &Foswiki::Plugins::EditTablePlugin::addJavaScriptInterfaceDisabledToHead
1294          ($tableNr)
1295          if ( $params{'changerows'} eq '' );
1296    }
1297    else {
1298        $params{editbutton} |= '';
1299
1300        # View mode
1301        if ( $params{editbutton} eq "hide" ) {
1302
1303            # do nothing, button assumed to be in a cell
1304        }
1305        else {
1306
1307            # Add edit button to end of table
1308            $text .=
1309              $preSp . viewEditCell("editbutton, 1, $params{'editbutton'}");
1310        }
1311    }
1312    return $text;
1313}
1314
1315=begin TML
1316
1317=cut
1318
1319sub parseEditCellFormat {
1320    $_[1] = Foswiki::Func::extractNameValuePair( $_[0] );
1321    return '';
1322}
1323
1324=begin TML
1325
1326=cut
1327
1328sub viewEditCell {
1329    my ($inAttr) = @_;
1330
1331    my $attributes = Foswiki::Func::extractNameValuePair($inAttr);
1332    return '' unless ( $attributes =~ /^editbutton/ );
1333
1334    $params{editbutton} = 'hide'
1335      unless ( $params{editbutton} );    # Hide below table edit button
1336
1337    my @bits = split( /,\s*/, $attributes );
1338    my $value = '';
1339    $value = $bits[2] if ( @bits > 2 );
1340    my $img = '';
1341    $img = $bits[3] if ( @bits > 3 );
1342
1343    unless ($value) {
1344        $value = $prefEDIT_BUTTON || '';
1345        $img = '';
1346        if ( $value =~ s/(.+),\s*(.+)/$1/o ) {
1347            $img = $2;
1348            $img =~ s|%ATTACHURL%|%PUBURL%/%SYSTEMWEB%/EditTablePlugin|;
1349            $img =~ s|%WEB%|%SYSTEMWEB%|;
1350        }
1351    }
1352    if ($img) {
1353        return
1354"<input class=\"editTableEditImageButton\" type=\"image\" src=\"$img\" alt=\"$value\" /> $warningMessage";
1355    }
1356    else {
1357        return
1358"<input class=\"foswikiButton editTableEditButton\" type=\"submit\" value=\"$value\" /> $warningMessage";
1359    }
1360}
1361
1362=begin TML
1363
1364=cut
1365
1366sub saveEditCellFormat {
1367    my ( $theFormat, $theName ) = @_;
1368
1369    return '' unless ($theFormat);
1370    $theName =~ s/cell/format/;
1371    return hiddenField( '', $theName, $theFormat, '' );
1372}
1373
1374=begin TML
1375
1376digestedCellValue: properly handle labels whose result may have been moved around by javascript, and therefore no longer correspond to the raw saved table text.
1377
1378=cut
1379
1380sub inputElement {
1381    my ( $inTableNr, $inRowNr, $inColumnNr, $inName, $inValue,
1382        $inDigestedCellValue, $inWeb, $inTopic )
1383      = @_;
1384
1385    my $rawValue = $inValue;
1386    my $text     = '';
1387    my $i        = @format - 1;
1388    $i = $inColumnNr if ( $inColumnNr < $i );
1389
1390    my @bits         = split( /,\s*/, $format[$i] );
1391    my @bitsExpanded = split( /,\s*/, $formatExpanded[$i] );
1392
1393    my $cellFormat = '';
1394    $inValue =~
1395      s/\s*$PATTERN_EDITCELL/&parseEditCellFormat( $1, $cellFormat )/e;
1396
1397    # If cell is empty we remove the space to not annoy the user when
1398    # he needs to add text to empty cell.
1399    $inValue = '' if ( $inValue eq ' ' );
1400
1401    if ($cellFormat) {
1402        my @aFormat = parseFormat( $cellFormat, $inTopic, $inWeb, 0 );
1403        @bits = split( /,\s*/, $aFormat[0] );
1404        @aFormat = parseFormat( $cellFormat, $inTopic, $inWeb, 1 );
1405        @bitsExpanded = split( /,\s*/, $aFormat[0] );
1406    }
1407
1408    my $type = 'text';
1409    $type = $bits[0] if @bits > 0;
1410
1411    # a table header is considered a label if read only header flag set
1412    $type = 'label'
1413      if ( ( $params{'headerislabel'} ) && ( $inValue =~ /^\s*\*.*\*\s*$/ ) );
1414    $type = 'label' if ( $type eq 'editbutton' );    # Hide [Edit table] button
1415    my $size = 0;
1416    $size = $bits[1] if @bits > 1;
1417
1418    my $val         = '';
1419    my $valExpanded = '';
1420    my $sel         = '';
1421
1422    if ( $type eq 'select' ) {
1423        my $expandedValue =
1424          Foswiki::Func::expandCommonVariables( $inValue, $inTopic, $inWeb );
1425        $size = 1 if $size < 1;
1426        $text =
1427          "<select class=\"foswikiSelect\" name=\"$inName\" size=\"$size\">";
1428        $i = 2;
1429        while ( $i < @bits ) {
1430            $val         = $bits[$i]         || '';
1431            $valExpanded = $bitsExpanded[$i] || '';
1432            $expandedValue =~ s/^\s+//;
1433            $expandedValue =~ s/\s+$//;
1434            $valExpanded   =~ s/^\s+//;
1435            $valExpanded   =~ s/\s+$//;
1436
1437            if ( $valExpanded eq $expandedValue ) {
1438                $text .= " <option selected=\"selected\">$val</option>";
1439            }
1440            else {
1441                $text .= " <option>$val</option>";
1442            }
1443            $i++;
1444        }
1445        $text .= "</select>";
1446        $text .= saveEditCellFormat( $cellFormat, $inName );
1447
1448    }
1449    elsif ( $type eq "radio" ) {
1450        my $expandedValue =
1451          &Foswiki::Func::expandCommonVariables( $inValue, $inTopic, $inWeb );
1452        $size = 1 if $size < 1;
1453        my $elements = ( @bits - 2 );
1454        my $lines    = $elements / $size;
1455        $lines = ( $lines == int($lines) ) ? $lines : int( $lines + 1 );
1456        $text .= "<table class=\"editTableInnerTable\"><tr><td valign=\"top\">"
1457          if ( $lines > 1 );
1458        $i = 2;
1459        while ( $i < @bits ) {
1460            $val         = $bits[$i]         || "";
1461            $valExpanded = $bitsExpanded[$i] || "";
1462            $expandedValue =~ s/^\s+//;
1463            $expandedValue =~ s/\s+$//;
1464            $valExpanded   =~ s/^\s+//;
1465            $valExpanded   =~ s/\s+$//;
1466            $text .= "<input type=\"radio\" name=\"$inName\" value=\"$val\"";
1467
1468            # make space to expand variables
1469            $val = addSpaceToBothSides($val);
1470            $text .= " checked=\"checked\""
1471              if ( $valExpanded eq $expandedValue );
1472            $text .= " />$val";
1473            if ( $lines > 1 ) {
1474
1475                if ( ( $i - 1 ) % $lines ) {
1476                    $text .= "<br />";
1477                }
1478                elsif ( $i - 1 < $elements ) {
1479                    $text .= "</td><td valign=\"top\">";
1480                }
1481            }
1482            $i++;
1483        }
1484        $text .= "</td></tr></table>" if ( $lines > 1 );
1485        $text .= saveEditCellFormat( $cellFormat, $inName );
1486
1487    }
1488    elsif ( $type eq "checkbox" ) {
1489        my $expandedValue =
1490          &Foswiki::Func::expandCommonVariables( $inValue, $inTopic, $inWeb );
1491        $size = 1 if $size < 1;
1492        my $elements = ( @bits - 2 );
1493        my $lines    = $elements / $size;
1494        my $names    = "Chkbx:";
1495        $lines = ( $lines == int($lines) ) ? $lines : int( $lines + 1 );
1496        $text .= "<table class=\"editTableInnerTable\"><tr><td valign=\"top\">"
1497          if ( $lines > 1 );
1498        $i = 2;
1499
1500        while ( $i < @bits ) {
1501            $val         = $bits[$i]         || "";
1502            $valExpanded = $bitsExpanded[$i] || "";
1503            $expandedValue =~ s/^\s+//;
1504            $expandedValue =~ s/\s+$//;
1505            $valExpanded   =~ s/^\s+//;
1506            $valExpanded   =~ s/\s+$//;
1507            $names .= " ${inName}x$i";
1508            $text .=
1509              " <input type=\"checkbox\" name=\"${inName}x$i\" value=\"$val\"";
1510
1511            $val = addSpaceToBothSides($val);
1512
1513            $text .= " checked=\"checked\""
1514              if ( $expandedValue =~ /(^|\s*,\s*)\Q$valExpanded\E(\s*,\s*|$)/ );
1515            $text .= " />$val";
1516
1517            if ( $lines > 1 ) {
1518                if ( ( $i - 1 ) % $lines ) {
1519                    $text .= "<br />";
1520                }
1521                elsif ( $i - 1 < $elements ) {
1522                    $text .= "</td><td valign=\"top\">";
1523                }
1524            }
1525            $i++;
1526        }
1527        $text .= "</td></tr></table>" if ( $lines > 1 );
1528        $text .= hiddenField( $preSp, $inName, $names );
1529        $text .= saveEditCellFormat( $cellFormat, $inName, "\n" );
1530
1531    }
1532    elsif ( $type eq 'row' ) {
1533        $size = $size + $inRowNr;
1534        $text =
1535            "<span class=\"et_rowlabel\">"
1536          . hiddenField( $size, $inName, $size )
1537          . "</span>";
1538        $text .= saveEditCellFormat( $cellFormat, $inName );
1539    }
1540    elsif ( $type eq 'label' ) {
1541
1542        # show label text as is, and add a hidden field with value
1543        my $isHeader = 0;
1544        $isHeader = 1 if ( $inValue =~ s/^\s*\*(.*)\*\s*$/$1/ );
1545        $text = $inValue;
1546
1547        # Replace CALC in labels with the fixed string CALC to avoid errors
1548        # when editing.
1549        _handleSpreadsheetFormula($text);
1550
1551        # To optimize things, only in the case where a read-only column is
1552        # being processed (inside of this unless() statement) do we actually
1553        # go out and read the original topic.  Thus the reason for the
1554        # following unless() so we only read the topic the first time through.
1555
1556        unless ( defined $tableMatrix{$inWeb}{$inTopic}
1557            and $inDigestedCellValue )
1558        {
1559
1560            # To deal with the situation where Foswiki variables, like
1561            # %CALC%, have already been processed and end up getting saved
1562            # in the table that way (processed), we need to read in the
1563            # topic page in raw format
1564            my $topicContents = Foswiki::Func::readTopicText(
1565                $Foswiki::Plugins::EditTablePlugin::web,
1566                $Foswiki::Plugins::EditTablePlugin::topic
1567            );
1568            parseTables( $topicContents, $inTopic, $inWeb );
1569        }
1570        my $table = $tableMatrix{$inWeb}{$inTopic};
1571        my $cell =
1572            $inDigestedCellValue
1573          ? $table->getCell( $inTableNr, $inRowNr - 1, $inColumnNr )
1574          : $rawValue;
1575        $inValue = $cell if ( defined $cell );    # original value from file
1576        Foswiki::Plugins::EditTablePlugin::encodeValue($inValue)
1577          unless ( $inValue eq '' );
1578
1579        #$inValue = "\*$inValue\*" if ( $isHeader and $inDigestedCellValue );
1580        $text = "\*$text\*" if $isHeader;
1581        $text .= ' ' . hiddenField( $preSp, $inName, $inValue );
1582    }
1583    elsif ( $type eq 'textarea' ) {
1584        my ( $rows, $cols ) = split( /x/, $size );
1585
1586        $rows |= 3  if !defined $rows;
1587        $cols |= 30 if !defined $cols;
1588        Foswiki::Plugins::EditTablePlugin::encodeValue($inValue)
1589          unless ( $inValue eq '' );
1590        $text .=
1591"<textarea class=\"foswikiTextarea editTableTextarea\" rows=\"$rows\" cols=\"$cols\" name=\"$inName\">$inValue</textarea>";
1592        $text .= saveEditCellFormat( $cellFormat, $inName );
1593
1594    }
1595    elsif ( $type eq 'date' ) {
1596
1597        # calendar format
1598        my $ifFormat = '';
1599        $ifFormat = $bits[3] if ( @bits > 3 );
1600        $ifFormat ||= $Foswiki::cfg{JSCalendarContrib}{format} || '%e %B %Y';
1601
1602        # protect format from parsing
1603        Foswiki::Plugins::EditTablePlugin::encodeValue($ifFormat);
1604
1605        $size = 10 if ( !$size || $size < 1 );
1606
1607        Foswiki::Plugins::EditTablePlugin::encodeValue($inValue)
1608          unless ( $inValue eq '' );
1609        $text .= CGI::textfield(
1610            {
1611                name     => $inName,
1612                class    => 'foswikiInputField editTableInput',
1613                id       => 'id' . $inName,
1614                size     => $size,
1615                value    => $inValue,
1616                override => 1
1617            }
1618        );
1619        $text .= saveEditCellFormat( $cellFormat, $inName );
1620        eval 'use Foswiki::Contrib::JSCalendarContrib';
1621
1622        unless ($@) {
1623            $text .= '<span class="foswikiMakeVisible">';
1624            $text .= CGI::image_button(
1625                -class   => 'editTableCalendarButton',
1626                -name    => 'calendar',
1627                -onclick => "return showCalendar('id$inName','$ifFormat')",
1628                -src     => Foswiki::Func::getPubUrlPath() . '/'
1629                  . $Foswiki::cfg{SystemWebName}
1630                  . '/JSCalendarContrib/img.gif',
1631                -alt   => 'Calendar',
1632                -align => 'middle'
1633            );
1634            $text .= '</span>';
1635        }
1636        $query->{'jscalendar'} = 1;
1637
1638        # prevent wrapping of button below input field
1639        $text = "<nobr>$text</nobr>";
1640    }
1641    else {    #  if( $type eq 'text')
1642        $size = $DEFAULT_FIELD_SIZE if $size < 1;
1643        Foswiki::Plugins::EditTablePlugin::encodeValue($inValue)
1644          unless ( $inValue eq '' );
1645        $text =
1646"<input class=\"foswikiInputField editTableInput\" type=\"text\" name=\"$inName\" size=\"$size\" value=\"$inValue\" />";
1647        $text .= saveEditCellFormat( $cellFormat, $inName );
1648    }
1649
1650    if ( $type ne 'textarea' ) {
1651        $text =~
1652          s/&#10;/<br \/>/g;    # change unicode linebreak character to <br />
1653    }
1654    return $text;
1655}
1656
1657=begin TML
1658
1659=cut
1660
1661sub handleTableRow {
1662    my (
1663        $thePre, $theRow, $theTableNr, $isNewRow, $theRowNr,
1664        $doEdit, $doSave, $inWeb,      $inTopic
1665    ) = @_;
1666
1667    _debug( "EditTablePlugin::Core::handleTableRow; params="
1668          . "\n\t thePre=$thePre."
1669          . "\n\t theRow=$theRow."
1670          . "\n\t theTableNr=$theTableNr"
1671          . "\n\t isNewRow=$isNewRow"
1672          . "\n\t theRowNr=$theRowNr"
1673          . "\n\t doEdit=$doEdit"
1674          . "\n\t doSave=$doSave"
1675          . "\n\t inWeb=$inWeb"
1676          . "\n\t inTopic=$inTopic" );
1677
1678    $thePre |= '';
1679    my $text = "$thePre\|";
1680
1681    if ($doEdit) {
1682        $theRow =~ s/\|\s*$//;
1683
1684        # retrieve any params sent by javascript interface (see edittable.js)
1685        my $rowID = $query->param("etrow_id$theRowNr");
1686        $rowID = $theRowNr if !defined $rowID;
1687
1688        my @cells;
1689        my $isNewRowFromHeader = ( $theRowNr <= 1 ) && ( $params{'header'} );
1690        @cells =
1691          $isNewRowFromHeader
1692          ? split( /\|/, $params{'header'} )
1693          : split( /\|/, $theRow );
1694        my $tmp = @cells;
1695        $nrCols = $tmp if ( $tmp > $nrCols );    # expand number of cols
1696        my $val         = '';
1697        my $cellFormat  = '';
1698        my $cell        = '';
1699        my $digested    = 0;
1700        my $cellDefined = 0;
1701        my $col         = 0;
1702
1703        while ( $col < $nrCols ) {
1704            $col += 1;
1705            $cellDefined = 0;
1706            my $cellValueParam = "etcell${rowID}x$col";
1707            $val = $isNewRow ? undef : $query->param($cellValueParam);
1708
1709           #my $tmpVal = defined $val ? $val : '';
1710           #_debug( "\t col=$col, cellValueParam=$cellValueParam; val=$tmpVal");
1711
1712            if ( defined $val && $val =~ /^Chkbx: (etcell.*)/ ) {
1713
1714      # Multiple checkboxes, val has format "Chkbx: etcell4x2x2 etcell4x2x3 ..."
1715                my $checkBoxNames  = $1;
1716                my $checkBoxValues = "";
1717                foreach ( split( /\s/, $checkBoxNames ) ) {
1718                    $val = $query->param($_);
1719
1720                    #$checkBoxValues .= "$val," if ( defined $val );
1721                    if ( defined $val ) {
1722
1723                        # make space to expand variables
1724                        $val = addSpaceToBothSides($val);
1725                        $checkBoxValues .= $val . ',';
1726                    }
1727                }
1728                $checkBoxValues =~ s/,\s*$//;
1729                $val = $checkBoxValues;
1730            }
1731
1732            # SMELL NOTE: etformat is not specified. What should it do?
1733            $cellFormat = $query->param("etformat${rowID}x$col");
1734            $val .= " %EDITCELL{$cellFormat}%" if ($cellFormat);
1735
1736            if ( defined $val ) {
1737
1738                # change any new line character sequences to <br />
1739                $val =~ s/[\n\r]{2,}?/<br \/>/gs;
1740
1741                # escape "|" to HTML entity
1742                $val =~ s/\|/\&\#124;/gs;
1743                $cellDefined = 1;
1744
1745                # Expand %-vars
1746                $cell = $val;
1747            }
1748            elsif ( $col <= @cells ) {
1749
1750                $cell = $cells[ $col - 1 ];
1751                $digested = 1;    # Flag that we are using non-raw cell text.
1752                $cellDefined = 1 if ( length($cell) > 0 );
1753                $cell =~ s/^\s*(.+?)\s*$/$1/
1754                  ; # remove spaces around content, but do not void a cell with just spaces
1755            }
1756            else {
1757                $cell = '';
1758            }
1759            if ($isNewRowFromHeader) {
1760
1761                unless ($cell) {
1762                    if ( $params{'header'} =~ /^on$/i ) {
1763                        if (   ( @format >= $col )
1764                            && ( $format[ $col - 1 ] =~ /(.*?)\,/ ) )
1765                        {
1766                            $cell = $1;
1767                        }
1768                        $cell = 'text' unless $cell;
1769                        $cell = "*$cell*";
1770                    }
1771                    else {
1772                        my @hCells = split( /\|/, $params{'header'} );
1773                        $cell = $hCells[ $col - 1 ] if ( @hCells >= $col );
1774                        $cell = "*text*" unless $cell;
1775                    }
1776                }
1777                $cell = addSpaceToBothSides($cell);
1778                $text .= "$cell\|";
1779            }
1780            elsif ($doSave) {
1781                $cell = addSpaceToBothSides($cell);
1782
1783            # Item5217 Avoid that deleting content of cell creates unwanted span
1784                $cell = ' ' if $cell eq '';
1785
1786                $text .= "$cell\|";
1787            }
1788            else {
1789                if (
1790                       ( !$cellDefined )
1791                    && ( @format >= $col )
1792                    && ( $format[ $col - 1 ] =~
1793                        /^\s*(.*?)\,\s*(.*?)\,\s*(.*?)\s*$/ )
1794                  )
1795                {
1796
1797                    # default value of "| text, 20, a, b, c |" cell is "a, b, c"
1798                    # default value of '| select, 1, a, b, c |' cell is "a"
1799                    $val = $1;    # type
1800
1801                    $cell = $3;
1802                    $cell = ''
1803                      unless ( defined $cell && $cell ne '' )
1804                      ;           # Proper handling of '0'
1805                    $cell =~ s/\,.*$//
1806                      if ( $val eq 'select' || $val eq 'date' );
1807                }
1808                my $element = '';
1809                $cell = '' if $isNewRow;
1810                $element =
1811                  inputElement( $theTableNr, $theRowNr, $col - 1,
1812                    "etcell${theRowNr}x$col", $cell, $digested, $inWeb,
1813                    $inTopic );
1814                $element = " $element \|";
1815                $text .= $element;
1816            }
1817        }
1818    }
1819    else {
1820
1821        # render EDITCELL in view mode
1822        $theRow =~ s/$PATTERN_EDITCELL/viewEditCell($1)/ge if !$doSave;
1823        $text .= $theRow;
1824
1825    }    # /if ($doEdit)
1826
1827    # render final value in view mode (not edit or save)
1828    Foswiki::Func::decodeFormatTokens($text)
1829      if ( !$doSave && !$doEdit );
1830
1831    return $text;
1832}
1833
1834=begin TML
1835
1836Add one space to both sides of the text to allow TML expansion.
1837Convert multiple (existing) spaces to one space.
1838
1839=cut
1840
1841sub addSpaceToBothSides {
1842    my ($text) = @_;
1843    return $text if $text eq '';
1844
1845    $text = " $text ";
1846    $text =~ s/^[[:space:]]+/ /;    # remove extra spaces
1847    $text =~ s/[[:space:]]+$/ /;
1848    return $text;
1849}
1850
1851=begin TML
1852
1853=cut
1854
1855sub doCancelEdit {
1856    my ( $inWeb, $inTopic ) = @_;
1857
1858    Foswiki::Func::setTopicEditLock( $inWeb, $inTopic, 0 );
1859
1860    Foswiki::Func::redirectCgiQuery( $query,
1861        Foswiki::Func::getViewUrl( $inWeb, $inTopic ) );
1862}
1863
1864=begin TML
1865
1866=cut
1867
1868sub doEnableEdit {
1869    my ( $inWeb, $inTopic, $doCheckIfLocked ) = @_;
1870
1871    my $wikiUserName = Foswiki::Func::getWikiName();
1872    if (
1873        !Foswiki::Func::checkAccessPermission(
1874            'change', $wikiUserName, undef, $inTopic, $inWeb
1875        )
1876      )
1877    {
1878
1879        # user has no permission to change the topic
1880        throw Foswiki::OopsException(
1881            'accessdenied',
1882            status => 403,
1883            def    => 'topic_access',
1884            web    => $inWeb,
1885            topic  => $inTopic,
1886            params => [ 'change', 'denied' ]
1887        );
1888    }
1889
1890    my $breakLock = $query->param('breaklock') || '';
1891    unless ($breakLock) {
1892        my ( $oopsUrl, $lockUser ) =
1893          Foswiki::Func::checkTopicEditLock( $inWeb, $inTopic, 'view' );
1894        if ($oopsUrl) {
1895            my $loginUser = Foswiki::Func::wikiToUserName($wikiUserName);
1896            if ( $lockUser ne $loginUser ) {
1897
1898                # change the default oopsleaseconflict url
1899                # use viewauth instead of view
1900                $oopsUrl =~ s/param4=view/param4=viewauth/;
1901
1902                # add info of the edited table
1903                my $params = '';
1904                $query = Foswiki::Func::getCgiQuery();
1905                $params .= ';ettablenr=' . $query->param('ettablenr');
1906                $params .= ';etedit=on';
1907                $oopsUrl =~ s/($|#\w*)/$params/;
1908
1909                # warn user that other person is editing this topic
1910                Foswiki::Func::redirectCgiQuery( $query, $oopsUrl );
1911                return 0;
1912            }
1913        }
1914    }
1915
1916    # We are allowed to edit
1917    Foswiki::Func::setTopicEditLock( $inWeb, $inTopic, 1 );
1918
1919    return 1;
1920}
1921
1922=begin TML
1923
1924stripCommentsFromRegex($pattern) -> $pattern
1925
1926For debugging: removes all spaces and comments from a regular expression.
1927
1928=cut
1929
1930sub stripCommentsFromRegex {
1931    my ($inRegex) = @_;
1932
1933    ( my $cleanRegex = $inRegex ) =~ s/\s*(.*?)\s*(#.*?)*(\r|\n|$)/$1/g;
1934    return $cleanRegex;
1935}
1936
1937=begin TML
1938
1939StaticMethod _handleSpreadsheetFormula( $text ) -> $text
1940
1941Replaces a SpreadSheetPlugin formula by a static text.
1942
1943=cut
1944
1945sub _handleSpreadsheetFormula {
1946
1947    return if !$_[0];
1948    $_[0] =~
1949      s/$PATTERN_SPREADSHEETPLUGIN_CALC/$SPREADSHEETPLUGIN_CALC_SUBSTITUTION/g;
1950
1951}
1952
1953=begin TML
1954
1955StaticMethod handleTmlInTables( \@lines )
1956
1957Users using the plugin would be confused when they enter newlines,
1958which get replaced with %BR%, and thus might not render their TML
1959
1960So we hack it here so that all TML and HTML tags have spaces around them:
1961- adds spaces around %BR% to render TML around linebreaks
1962- add spaces around TML next to HTML tags, again to render TML
1963- expands variables, for example %CALC%
1964Check Foswikibug:Item1017
1965
1966=cut
1967
1968sub handleTmlInTables {
1969
1970    # my $lines = $_[0]
1971
1972    map { $_ =~ s/(%BR%)/ $1 /gx; addSpacesToTmlNextToHtml($_) } @{ $_[0] };
1973}
1974
1975=begin TML
1976
1977StaticMethod addSpacesToTmlNextToHtml( \$text )
1978
1979So that:
1980
1981| *bold*<br />_italic_ |
1982
1983gets rendered as:
1984
1985|*bold* <br /> _italic_|
1986
1987=cut
1988
1989sub addSpacesToTmlNextToHtml {
1990
1991    # my $text = $_[0]
1992
1993    # also remove spaces at both sides to prevent extra spaces are added to the
1994    # cell, resulting in wrong alignment (when html tags are stripped in the
1995    # core table renderer)
1996
1997    my $TMLpattern = qr/[_*=]*/;
1998    my $pattern    = qr(
1999	[[:space:]]*		# any space
2000	($TMLpattern)		# i1: optional TML syntax before html tag
2001	(					# i2: html tag
2002	</*				# start of tag (optional closing tag)
2003	(?:$HTML_TAGS)+		# any of the html tags
2004	[[:space:]]*     	# any space
2005	.*?				    # anything before the end of tag
2006	/*>				# end of tag (optional closing tag)
2007	)					# /i2
2008	($TMLpattern)		# i3: optional TML syntax after html tag
2009	[[:space:]]*		# any space
2010	)x;
2011
2012    $_[0] =~ s/$pattern/$1 $2 $3/g;
2013}
2014
2015=begin TML
2016
2017StaticMethod getHeaderAndFooterCount( $text ) -> ($headerRowCount, $footerRowCount)
2018
2019Reads the headerrows and footerrows parameters from the TABLE macro (if any) and returns them as tuple.
2020
2021If no TABLE tag is present, returns (0,0).
2022
2023=cut
2024
2025sub getHeaderAndFooterCount {
2026    my ($inTag) = @_;
2027
2028    my $tag = $inTag;
2029
2030    # expand macros in tagline without creating infinite recursion,
2031    # so delete EDITTABLE as we won't need it here
2032    $tag =~ s/%EDITTABLE\{/_DELETED_/;
2033    $tag = Foswiki::Func::expandCommonVariables($tag);
2034
2035    my $headerRowCount = 0;
2036    my $footerRowCount = 0;
2037
2038    if ( $tag =~ m/$PATTERN_TABLEPLUGIN/ ) {
2039
2040        # We want this info also when viewing, because the row count takes
2041        # header and footer rows into account
2042        # match with a TablePlugin line
2043        # works when TABLE tag is just above OR just below the EDITTABLE tag
2044        my %tablePluginParams = Foswiki::Func::extractParameters($1);
2045        $headerRowCount = $tablePluginParams{'headerrows'} || 0;
2046        $footerRowCount = $tablePluginParams{'footerrows'} || 0;
2047    }
2048    return ( $headerRowCount, $footerRowCount );
2049}
2050
2051sub _modeToString {
2052    my ($mode) = @_;
2053
2054    my $text = '';
2055    $text .= "; mode is READ"           if ( $mode & $MODE->{READ} );
2056    $text .= "; mode is EDIT"           if ( $mode & $MODE->{EDIT} );
2057    $text .= "; mode is SAVE"           if ( $mode & $MODE->{SAVE} );
2058    $text .= "; mode is SAVEQUIET"      if ( $mode & $MODE->{SAVEQUIET} );
2059    $text .= "; mode is CANCEL"         if ( $mode & $MODE->{CANCEL} );
2060    $text .= "; mode is EDITNOTALLOWED" if ( $mode & $MODE->{EDITNOTALLOWED} );
2061
2062    return $text;
2063}
2064
2065sub _writeDebug {
2066    my ($inText) = @_;
2067
2068    Foswiki::Func::writeDebug($inText);
2069}
2070
2071sub _debug {
2072    my ($inText) = @_;
2073
2074    Foswiki::Func::writeDebug($inText)
2075      if $Foswiki::Plugins::EditTablePlugin::debug;
2076}
2077
20781;
2079
2080__END__
2081Foswiki - The Free and Open Source Wiki, http://foswiki.org/
2082
2083Copyright (C) 2008-2011 Foswiki Contributors. Foswiki Contributors
2084are listed in the AUTHORS file in the root of this distribution.
2085NOTE: Please extend that file, not this notice.
2086
2087Additional copyrights apply to some or all of the code in this
2088file as follows:
2089
2090Copyright (C) 2008-2009 Arthur Clemens, arthur@visiblearea.com
2091and Foswiki contributors
2092Copyright (C) 2002-2007 Peter Thoeny, peter@thoeny.org and
2093TWiki Contributors.
2094
2095This program is free software; you can redistribute it and/or
2096modify it under the terms of the GNU General Public License
2097as published by the Free Software Foundation; either version 2
2098of the License, or (at your option) any later version. For
2099more details read LICENSE in the root of this distribution.
2100
2101This program is distributed in the hope that it will be useful,
2102but WITHOUT ANY WARRANTY; without even the implied warranty of
2103MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
2104
2105As per the GPL, removal of this notice is prohibited.
2106