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