Index: TWiki.pm =================================================================== --- TWiki.pm (revision 14684) +++ TWiki.pm (working copy) @@ -2087,55 +2087,62 @@ my $seq = 0; my $ntext = ''; my $offset = 0; - foreach my $bit (split(/(%(?:START|END)SECTION(?:{.*?})?%)/, $_[0] )) { - if( $bit =~ /^%STARTSECTION(?:{(.*)})?%$/) { + foreach my $bit (split(/(%(?:START|END|ELSE)SECTION(?:{.*?})?%)/, $_[0] )) { + if( $bit =~ /^%(START|END|ELSE)SECTION(?:{(.*)})?%$/) { + my $tag = $1; require TWiki::Attrs; - my $attrs = new TWiki::Attrs( $1 ); - $attrs->{type} ||= 'section'; - $attrs->{name} = $attrs->{_DEFAULT} || $attrs->{name} || - '_SECTION'.$seq++; + my $attrs = new TWiki::Attrs( $2 ); + $attrs->{name} = $attrs->{_DEFAULT} || $attrs->{name}; delete $attrs->{_DEFAULT}; - my $id = $attrs->{type}.':'.$attrs->{name}; - if( $sections{$id} ) { - # error, this named section already defined, ignore - next; - } - # close open unnamed sections of the same type - foreach my $s ( @list ) { - if( $s->{end} < 0 && $s->{type} eq $attrs->{type} && - $s->{name} =~ /^_SECTION\d+$/ ) { - $s->{end} = $offset; + if ($tag eq 'START') { + $attrs->{name} ||= '_SECTION'.$seq++; + $attrs->{type} ||= 'section'; + my $id = $attrs->{type}.':'.$attrs->{name}; + if( $sections{$id} ) { + # error, this named section already defined, ignore + next; } - } - $attrs->{start} = $offset; - $attrs->{end} = -1; # open section - $sections{$id} = $attrs; - push( @list, $attrs ); - } elsif( $bit =~ /^%ENDSECTION(?:{(.*)})?%$/ ) { - require TWiki::Attrs; - my $attrs = new TWiki::Attrs( $1 ); - $attrs->{type} ||= 'section'; - $attrs->{name} = $attrs->{_DEFAULT} || $attrs->{name} || ''; - delete $attrs->{_DEFAULT}; - unless( $attrs->{name} ) { - # find the last open unnamed section of this type - foreach my $s ( reverse @list ) { - if( $s->{end} == -1 && - $s->{type} eq $attrs->{type} && - $s->{name} =~ /^_SECTION\d+$/ ) { - $attrs->{name} = $s->{name}; - last; + # close open unnamed sections of the same type + foreach my $s ( @list ) { + if( $s->{end} < 0 && $s->{type} eq $attrs->{type} && + $s->{name} =~ /^_SECTION\d+$/ ) { + $s->{end} = $offset; } } - # ignore it if no matching START found - next unless $attrs->{name}; + $attrs->{start} = $offset; + $attrs->{end} = -1; # open section + $sections{$id} = $attrs; + push( @list, $attrs ); + } elsif( $tag eq 'END' || $tag eq 'ELSE' ) { + $attrs->{name} ||= ''; + unless( $attrs->{name} ) { + # find the last open unnamed section of this type + # or, if there is no type, the last open section + # of any type + foreach my $s ( reverse @list ) { + if( $s->{end} == -1 && + ( !$attrs->{type} || + $s->{type} eq $attrs->{type} ) && + $s->{name} =~ /^_SECTION\d+$/ ) { + $attrs->{name} = $s->{name}; + $attrs->{type} = $s->{type}; + last; + } + } + # ignore it if no matching START found + next unless $attrs->{name}; + } + my $id = $attrs->{type}.':'.$attrs->{name}; + if( !$sections{$id} || $sections{$id}->{end} >= 0 ) { + # error, no such open section, ignore + next; + } + if( $tag eq 'ELSE' ) { + $sections{$id}->{else} = $offset; + } else { + $sections{$id}->{end} = $offset; + } } - my $id = $attrs->{type}.':'.$attrs->{name}; - if( !$sections{$id} || $sections{$id}->{end} >= 0 ) { - # error, no such open section, ignore - next; - } - $sections{$id}->{end} = $offset; } else { $ntext .= $bit; $offset = length( $ntext ); @@ -2150,6 +2157,27 @@ return( $ntext, \@list ); } +# Patch the section tags for $section back into $text. Note that this +# must always be done in reverse (last section first) to make sure section +# offsets remain valid. +sub _restoreSection { + my( $s, $t ) = @_; + my $start = $s->remove('start'); + my $else = $s->remove('else'); + my $end = $s->remove('end'); + my $ntext = substr($t, 0, $start). + '%STARTSECTION{'.$s->stringify().'}%'; + if( $else >= 0 ) { + $ntext .= substr($t, $start, $else - $start). + '%ELSE{'.$s->stringify().'}%'; + $start = $else; + } + $ntext .= substr($t, $start, $end - $start). + '%ENDSECTION{'.$s->stringify().'}%'. + substr($t, $end, length($t)); + return $ntext; +} + =pod ---++ ObjectMethod expandVariablesOnTopicCreation ( $text, $user, $web, $topic ) -> $text @@ -2173,20 +2201,16 @@ # Chop out templateonly sections my( $ntext, $sections ) = parseSections( $text ); if( scalar( @$sections )) { - # Note that if named templateonly sections overlap, the behaviour is undefined. + # Note that if named templateonly sections overlap, the + # behaviour is undefined. foreach my $s ( reverse @$sections ) { if( $s->{type} eq 'templateonly' ) { + # Note: else clause is ignored for templateonly sections $ntext = substr($ntext, 0, $s->{start}) . substr($ntext, $s->{end}, length($ntext)); } else { # put back non-templateonly sections - my $start = $s->remove('start'); - my $end = $s->remove('end'); - $ntext = substr($ntext, 0, $start). - '%STARTSECTION{'.$s->stringify().'}%'. - substr($ntext, $start, $end - $start). - '%ENDSECTION{'.$s->stringify().'}%'. - substr($ntext, $end, length($ntext)); + $ntext = _restoreSection( $s, $ntext ); } } $text = $ntext; @@ -2210,6 +2234,7 @@ $theTopic ||= $this->{session}->{topicName}; foreach my $s ( reverse @$sections ) { if( $s->{type} eq 'expandvariables' ) { + # Note: else clause is ignored for expandvariables sections my $etext = substr( $ntext, $s->{start}, $s->{end} - $s->{start} ); expandAllTags( $this, \$etext, $theTopic, $theWeb ); $ntext = substr( $ntext, 0, $s->{start}) @@ -2217,13 +2242,7 @@ . substr( $ntext, $s->{end}, length($ntext) ); } else { # put back non-expandvariables sections - my $start = $s->remove('start'); - my $end = $s->remove('end'); - $ntext = substr($ntext, 0, $start). - '%STARTSECTION{'.$s->stringify().'}%'. - substr($ntext, $start, $end - $start). - '%ENDSECTION{'.$s->stringify().'}%'. - substr($ntext, $end, length($ntext)); + $ntext = _restoreSection( $s, $ntext ); } } $text = $ntext; @@ -2812,45 +2831,78 @@ =cut sub handleCommonTags { - my( $this, $text, $theWeb, $theTopic, $meta ) = @_; + my( $this, $text, $web, $topic, $meta ) = @_; - ASSERT($theWeb) if DEBUG; - ASSERT($theTopic) if DEBUG; + ASSERT($web) if DEBUG; + ASSERT($topic) if DEBUG; return $text unless $text; - my $verbatim={}; + my $verbatim = {}; + # Plugin Hook (for cache Plugins only) - $this->{plugins}->beforeCommonTagsHandler( - $text, $theTopic, $theWeb, $meta ); + $this->{plugins}->beforeCommonTagsHandler( $text, $topic, $web, $meta ); - #use a "global var", so included topics can extract and putback - #their verbatim blocks safetly. - $text = $this->renderer->takeOutBlocks( $text, 'verbatim', - $verbatim); + $text = $this->renderer->takeOutBlocks( $text, 'verbatim', $verbatim); + $text = $this->renderer->takeOutBlocks( $text, 'literal', $verbatim); + # Handle conditional sections + my( $ntext, $sections ) = parseSections( $text ); + $text = $ntext; + if( scalar( @$sections )) { + foreach my $s ( reverse @$sections ) { + if( $s->{type} eq 'if' ) { + my $start = $s->{start}; + my $end = $s->{end}; + my $else = $s->{else}; + unless( $ifParser ) { + require TWiki::If::Parser; + $ifParser = new TWiki::If::Parser(); + } + # Must explicitly expand tags embedded in the condition, + # because this comes before inner tags are expanded + my $condition = $this->handleCommonTags( + $s->{condition}, $web, $topic, $meta ); + my $expr = $ifParser->parse( $s->{condition} || '1' ); + if( $expr->evaluate( tom => $meta, data => $meta )) { + if( defined $else) { + $start = $else; + } + } else { + if( defined $else) { + $end = $else; + } + } + $text = substr($text, 0, $start) + . substr($text, $end, length($text)); + } else { + # put back non-if sections + $text = _restoreSection( $s, $text ); + } + } + } + my $memW = $this->{SESSION_TAGS}{INCLUDINGWEB}; my $memT = $this->{SESSION_TAGS}{INCLUDINGTOPIC}; - $this->{SESSION_TAGS}{INCLUDINGWEB} = $theWeb; - $this->{SESSION_TAGS}{INCLUDINGTOPIC} = $theTopic; + $this->{SESSION_TAGS}{INCLUDINGWEB} = $web; + $this->{SESSION_TAGS}{INCLUDINGTOPIC} = $topic; - expandAllTags( $this, \$text, $theTopic, $theWeb, $meta ); + expandAllTags( $this, \$text, $topic, $web, $meta ); - $text = $this->renderer->takeOutBlocks( $text, 'verbatim', - $verbatim); + $text = $this->renderer->takeOutBlocks( $text, 'verbatim', $verbatim); + $text = $this->renderer->takeOutBlocks( $text, 'literal', $verbatim); - # Plugin Hook - $this->{plugins}->commonTagsHandler( $text, $theTopic, $theWeb, 0, $meta ); + $this->{plugins}->commonTagsHandler( $text, $topic, $web, 0, $meta ); # process tags again because plugin hook may have added more in - expandAllTags( $this, \$text, $theTopic, $theWeb, $meta ); + expandAllTags( $this, \$text, $topic, $web, $meta ); $this->{SESSION_TAGS}{INCLUDINGWEB} = $memW; $this->{SESSION_TAGS}{INCLUDINGTOPIC} = $memT; # 'Special plugin tag' TOC hack, must be done after all other expansions # are complete, and has to reprocess the entire topic. - $text =~ s/%TOC(?:{(.*?)})?%/$this->_TOC($text, $theTopic, $theWeb, $1)/ge; + $text =~ s/%TOC(?:{(.*?)})?%/$this->_TOC($text, $topic, $web, $1)/ge; # Codev.FormattedSearchWithConditionalOutput: remove lines, # possibly introduced by SEARCHes with conditional CALC. This needs @@ -2858,11 +2910,11 @@ # table rows properly $text =~ s/^\r?\n//gm; + $this->renderer->putBackBlocks( \$text, $verbatim, 'literal' ); $this->renderer->putBackBlocks( \$text, $verbatim, 'verbatim' ); # TWiki Plugin Hook (for cache Plugins only) - $this->{plugins}->afterCommonTagsHandler( - $text, $theTopic, $theWeb, $meta ); + $this->{plugins}->afterCommonTagsHandler( $text, $topic, $web, $meta ); return $text; } @@ -3183,14 +3235,19 @@ # Rebuild the text from the interesting sections $text = ''; foreach my $s ( @$sections ) { - if( $section && $s->{type} eq 'section' && - $s->{name} eq $section) { + if( $section && $s->{type} eq 'section' + && $s->{name} eq $section) { + # else clause is ignored for 'section' sections $text .= substr( $ntext, $s->{start}, $s->{end}-$s->{start} ); $interesting = 1; last; } elsif( $s->{type} eq 'include' && !$section ) { + # else clause is ignored for 'include' sections $text .= substr( $ntext, $s->{start}, $s->{end}-$s->{start} ); $interesting = 1; + } else { + # put back other sections + $text .= _restoreSection( $s, $ntext ); } } }