--- lib/TWiki.pm.org 2008-08-12 09:21:47.000000000 +0200 +++ lib/TWiki.pm 2008-08-12 16:12:24.000000000 +0200 @@ -2000,45 +1996,69 @@ } - # SMELL: this handling of
 is archaic.
-    # SMELL: use forEachLine
-    foreach my $line ( split( /\r?\n/, $text ) ) {
-        my $level;
-        if ( $line =~ m/$regex{headerPatternDa}/o ) {
-            $line = $2;
-            $level = length $1;
-        } elsif ( $line =~ m/$regex{headerPatternHt}/io ) {
-            $line = $2;
-            $level = $1;
-        } else {
-            next;
-        }
+    # NB: While we're processing $text line by line here,
+    # $this->renderer->getRendereredVersion() 'allocates' unique anchor names by
+    # first replacing '#WikiWord', followed by regex{headerPatternHt} and
+    # regex{headerPatternDa}. In order to stay in sync and not 'clutter'/slow
+    # down the renderer code, we have to adhere to this order here as well
+    my @regexps = ('^(\#)('.$regex{wikiWordRegex}.')',
+                   $regex{headerPatternHt},
+                   $regex{headerPatternDa});
+    my @lines = split( /\r?\n/, $text );
+    my %anchors = ();
+    my %headings = ();
+    my %levels = ();
+    for my $i (0 .. $#regexps) {
+        my $lineno = 0;
+        # SMELL: use forEachLine
+        foreach my $line (@lines) {
+            $lineno++;
+            if ($line =~ m/$regexps[$i]/) {
+                my ($level, $heading) = ($1, $2);
+                my $anchor = $this->renderer->makeUniqueAnchorName($heading);
+
+                if ($i > 0) {
+                    # SMELL: needed only because Render::_makeAnchorHeading uses it
+                    my $compatAnchor = $this->renderer->makeAnchorName($anchor, 1);
+                    $compatAnchor = $this->renderer->makeUniqueAnchorName($anchor,1)
+                        if ($compatAnchor ne $anchor);
+
+                    $heading =~ s/\s*$regex{headerPatternNoTOC}.+$//go;
+                    next unless $heading;
+
+                    $level = length $level if ($i == 2);
+                    if( ($level >= $minDepth) && ($level <= $maxDepth) ) {
+                        $anchors{$lineno} = $anchor;
+                        $headings{$lineno} = $heading;
+                        $levels{$lineno} = $level;
+		    }
+		}
+	    }
+	}
+    }
 
-        if( $line && ($level >= $minDepth) && ($level <= $maxDepth) ) {
-            # cut TOC exclude '---+ heading !! exclude this bit'
-            $line =~ s/\s*$regex{headerPatternNoTOC}.+$//go;
-            next unless $line;
-            my $anchor = $this->renderer->makeAnchorName( $line );
-            $highest = $level if( $level < $highest );
-            my $tabs = "\t" x $level;
-            # Remove *bold*, _italic_ and =fixed= formatting
-            $line =~ s/(^|[\s\(])\*([^\s]+?|[^\s].*?[^\s])\*($|[\s\,\.\;\:\!\?\)])/$1$2$3/g;
-            $line =~ s/(^|[\s\(])_+([^\s]+?|[^\s].*?[^\s])_+($|[\s\,\.\;\:\!\?\)])/$1$2$3/g;
-            $line =~ s/(^|[\s\(])=+([^\s]+?|[^\s].*?[^\s])=+($|[\s\,\.\;\:\!\?\)])/$1$2$3/g;
-            # Prevent WikiLinks
-            $line =~ s/\[\[.*?\]\[(.*?)\]\]/$1/g;  # '[[...][...]]'
-            $line =~ s/\[\[(.*?)\]\]/$1/ge;        # '[[...]]'
-            $line =~ s/([\s\(])($regex{webNameRegex})\.($regex{wikiWordRegex})/$1$3/go;  # 'Web.TopicName'
-            $line =~ s/([\s\(])($regex{wikiWordRegex})/$1$2/go;  # 'TopicName'
-            $line =~ s/([\s\(])($regex{abbrevRegex})/$1$2/go;    # 'TLA'
-            $line =~ s/([\s\-\*\(])([$regex{mixedAlphaNum}]+\:)/$1$2/go; # 'Site:page' Interwiki link
-            # Prevent manual links
-            $line =~ s/<[\/]?a\b[^>]*>//gi;
-            # create linked bullet item, using a relative link to anchor
-            my $target = $isSameTopic ?
-                         _make_params(0, '#'=>$anchor,@qparams) :
-                         $this->getScriptUrl(0,'view',$web,$topic,'#'=>$anchor,@qparams);
-            $line = $tabs.'* ' .  CGI::a({href=>$target},$line);
-            $result .= "\n".$line;
-        }
+    # SMELL: this handling of 
 is archaic.
+    foreach my $lineno (sort{$a <=> $b}(keys %headings)) {
+        my ($level, $line, $anchor) = ($levels{$lineno}, $headings{$lineno}, $anchors{$lineno});
+        $highest = $level if( $level < $highest );
+        my $tabs = "\t" x $level;
+        # Remove *bold*, _italic_ and =fixed= formatting
+        $line =~ s/(^|[\s\(])\*([^\s]+?|[^\s].*?[^\s])\*($|[\s\,\.\;\:\!\?\)])/$1$2$3/g;
+        $line =~ s/(^|[\s\(])_+([^\s]+?|[^\s].*?[^\s])_+($|[\s\,\.\;\:\!\?\)])/$1$2$3/g;
+        $line =~ s/(^|[\s\(])=+([^\s]+?|[^\s].*?[^\s])=+($|[\s\,\.\;\:\!\?\)])/$1$2$3/g;
+        # Prevent WikiLinks
+        $line =~ s/\[\[.*?\]\[(.*?)\]\]/$1/g;  # '[[...][...]]'
+        $line =~ s/\[\[(.*?)\]\]/$1/ge;        # '[[...]]'
+        $line =~ s/([\s\(])($regex{webNameRegex})\.($regex{wikiWordRegex})/$1$3/go;  # 'Web.TopicName'
+        $line =~ s/([\s\(])($regex{wikiWordRegex})/$1$2/go;  # 'TopicName'
+        $line =~ s/([\s\(])($regex{abbrevRegex})/$1$2/go;    # 'TLA'
+        $line =~ s/([\s\-\*\(])([$regex{mixedAlphaNum}]+\:)/$1$2/go; # 'Site:page' Interwiki link
+        # Prevent manual links
+        $line =~ s/<[\/]?a\b[^>]*>//gi;
+        # create linked bullet item, using a relative link to anchor
+        my $target = $isSameTopic ?
+                     _make_params(0, '#'=>$anchor,@qparams) :
+                     $this->getScriptUrl(0,'view',$web,$topic,'#'=>$anchor,@qparams);
+        $line = $tabs.'* ' .  CGI::a({href=>$target},$line);
+        $result .= "\n".$line;
     }
 
--- lib/TWiki/Render.pm.org	2008-08-11 12:49:41.000000000 +0200
+++ lib/TWiki/Render.pm	2008-08-12 14:56:24.000000000 +0200
@@ -21,4 +21,7 @@
 $placeholderMarker = 0;
 
+# Used to generate unique anchors
+my %anchornames = ();
+
 # defaults for trunctation of summary text
 my $TMLTRUNC = 162;
@@ -355,12 +358,16 @@
     # - Initial '' is needed to prevent subsequent matches.
     # - filter out $TWiki::regex{headerPatternNoTOC} ( '!!' and '%NOTOC%' )
-    my $anchorName =       $this->makeAnchorName( $text, 0 );
-    my $compatAnchorName = $this->makeAnchorName( $text, 1 );
+    my $anchorName = $this->makeUniqueAnchorName( $text, 0 );
+    #  if the generated uniqe anchor name is 'compatible', it won't change:
+    my $compatAnchorName = $this->makeAnchorName( $anchorName, 1 );
+
     # filter '!!', '%NOTOC%'
     $text =~ s/$TWiki::regex{headerPatternNoTOC}//o;
     my $html = '';
     $html .= CGI::a( { name => $anchorName }, '' );
-    $html .= CGI::a( { name => $compatAnchorName }, '')
-      if( $compatAnchorName ne $anchorName );
+    if( $compatAnchorName ne $anchorName ) {
+        $compatAnchorName = $this->makeUniqueAnchorName( $anchorName, 1 );
+        $html .= CGI::a( { name => $compatAnchorName }, '');
+    }
     $html .= ' '.$text.' ';
 
@@ -428,4 +435,36 @@
 }
 
+
+=pod
+
+---++ ObjectMethod makeUniqueAnchorName($anchorName, $compatibility) -> $anchorName
+
+   * =$anchorName= - the unprocessed anchor name
+   * =$compatibilityMode= - SMELL: compatibility with *what*?? Who knows. :-(
+
+Build a valid HTML anchor name (unique w.r.t. the list stored in %anchornames)
+
+=cut
+
+sub makeUniqueAnchorName {
+    my( $this, $text, $compatibilityMode ) = @_;
+
+    my $anchorName = $this->makeAnchorName( $text, $compatibilityMode );
+
+    # ensure that the generated anchor name is unique
+    my $cnt = 2;
+    my $suffix = '';
+    while (exists $anchornames{$anchorName . $suffix}) {
+        $suffix = '_' . $cnt++;
+        # limit resulting name to 32 chars
+        $anchorName = substr($anchorName, 0, 32 - length($suffix));
+    }
+    $anchorName .= $suffix;
+    $anchornames{$anchorName} = 1;
+
+    return $anchorName;
+}
+
+
 # Returns =title='...'= tooltip info in case LINKTOOLTIPINFO perferences variable is set. 
 # Warning: Slower performance if enabled.
@@ -884,4 +923,6 @@
     @{$this->{LIST}} = ();
 
+    %anchornames = ();
+
     # Initial cleanup
     $text =~ s/\r//g;
@@ -1000,4 +1041,8 @@
     $text =~ s/$TWiki::TranslationToken(\w+;)/&$1/go;
 
+    # '#WikiName' anchors (moved in front in order to retain original names if possible)
+    # SMELL: in case this type of anchor (presumably user-defined) gets renamed, it should be noted somewhere
+    $text =~ s/^(\#)($TWiki::regex{wikiWordRegex})/CGI::a({name=>$this->makeUniqueAnchorName($2)},'')/geom;
+
     # Headings
     # '
...
' HTML rule @@ -1105,6 +1150,5 @@ $text = join( '', @result ); - # '#WikiName' anchors - $text =~ s/^(\#)($TWiki::regex{wikiWordRegex})/CGI::a( { name=>$this->makeAnchorName( $2 )}, '')/geom; + # WikiWords $text =~ s/${STARTWW}==(\S+?|\S[^\n]*?\S)==$ENDWW/_fixedFontText($1,1)/gem; $text =~ s/${STARTWW}__(\S+?|\S[^\n]*?\S)__$ENDWW/$1<\/em><\/strong>/gm;