--- lib/TWiki.pm.org 2008-08-12 09:21:47.000000000 +0200 +++ lib/TWiki.pm 2008-08-12 09:26:52.000000000 +0200 @@ -2000,7 +1996,32 @@ } + # NB: While we're processing $text line by line here, $this->renderer->getRendereredVersion() + # 'allocates' unique anchor names by first replacing regex{headerPatternHt} followed by + # regex{headerPatternDa}. In order to stay in sync and not 'clutter'/slow down the + # renderer code, we have to mimick the procedure here + my @regexps = ($regex{headerPatternHt}, $regex{headerPatternDa}); + my @lines = split( /\r?\n/, $text ); + my %anchors = (); + foreach my $regexp (@regexps) { + my $lineno = 0; + foreach my $line (@lines) { + $lineno++; + if ($line =~ m/$regexp/) { + $line =~ s/\s*$regex{headerPatternNoTOC}.+$//go; + next unless $line; + my ($anchor, $compatAnchor) = $this->renderer->makeUniqueAnchorNames( $line ); + $anchors{$lineno} = $anchor; + } + } + } + + # The following is still not optimised--there's no need for a third iteration over array + # @lines if we store all needed information (e.g., headings) along with anchors above... + # SMELL: this handling of
 is archaic.
     # SMELL: use forEachLine
-    foreach my $line ( split( /\r?\n/, $text ) ) {
+    my $lineno = 0;    
+    foreach my $line (@lines) {
+        $lineno++;
         my $level;
         if ( $line =~ m/$regex{headerPatternDa}/o ) {
@@ -2018,5 +2039,5 @@
             $line =~ s/\s*$regex{headerPatternNoTOC}.+$//go;
             next unless $line;
-            my $anchor = $this->renderer->makeAnchorName( $line );
+            my $anchor = $anchors{$lineno};
             $highest = $level if( $level < $highest );
             my $tabs = "\t" x $level;
--- lib/TWiki/Render.pm.org	2008-08-11 12:49:41.000000000 +0200
+++ lib/TWiki/Render.pm	2008-08-12 09:35:50.000000000 +0200
@@ -21,4 +21,7 @@
 $placeholderMarker = 0;
 
+# Used to generate unique anchors
+my %headings = ();
+
 # defaults for trunctation of summary text
 my $TMLTRUNC = 162;
@@ -355,6 +358,6 @@
     # - 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, $compatAnchorName) = $this->makeUniqueAnchorNames( $text );
+
     # filter '!!', '%NOTOC%'
     $text =~ s/$TWiki::regex{headerPatternNoTOC}//o;
@@ -428,4 +431,42 @@
 }
 
+=pod
+
+---++ ObjectMethod makeUniqueAnchorNames($anchorName) -> ($anchorName, $compatAnchorName)
+
+   * =$anchorName= - the unprocessed anchor name
+   * =$compatAnchorName= - SMELL: compatibility with *what*?? Who knows. :-(
+
+Build a valid pair of HTML anchor names (deemed unique w.r.t. the list stored in %headings)
+
+=cut
+
+sub makeUniqueAnchorNames {
+    my( $this, $text ) = @_;
+
+    # - idea: call makeAnchorName() 'pairwise' to make sure that the generated
+    #   anchor names will be unique in both cases
+    my $anchorName =       $this->makeAnchorName( $text, 0 );
+    my $compatAnchorName = $this->makeAnchorName( $text, 1 );
+
+    # ensure that all generated anchor names are unique
+    my $cnt = 2;
+    my $suffix = '';
+    while ((exists $headings{$anchorName . $suffix}) ||
+           (exists $headings{$compatAnchorName . $suffix})) {
+        $suffix = '_' . $cnt++;
+        # limit resulting names to 32 chars
+        $anchorName = substr($anchorName, 0, 32 - length($suffix));
+        $compatAnchorName = substr($compatAnchorName, 0, 32 - length($suffix));
+    }
+    $anchorName .= $suffix;
+    $headings{$anchorName} = 1;
+    $compatAnchorName .= $suffix;
+    $headings{$compatAnchorName} = 1;
+
+    return ($anchorName, $compatAnchorName);
+}
+
+
 # Returns =title='...'= tooltip info in case LINKTOOLTIPINFO perferences variable is set. 
 # Warning: Slower performance if enabled.
@@ -884,4 +925,6 @@
     @{$this->{LIST}} = ();
 
+    %headings = ();
+
     # Initial cleanup
     $text =~ s/\r//g;