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 <nop> lines,
     # possibly introduced by SEARCHes with conditional CALC. This needs
@@ -2858,11 +2910,11 @@
     # table rows properly
     $text =~ s/^<nop>\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 );
             }
         }
     }
