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/ /<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