Core changes for NavigableTOC extension:
authorRoan Kattouw <catrope@users.mediawiki.org>
Thu, 25 Jun 2009 11:05:22 +0000 (11:05 +0000)
committerRoan Kattouw <catrope@users.mediawiki.org>
Thu, 25 Jun 2009 11:05:22 +0000 (11:05 +0000)
* Always generate the section tree, even when we're not generating a TOC
* Add Parser::mergeSectionTrees() to merge two section trees into one
* Add Linker::generateTOC() to generate the HTML for a TOC from a section tree, and add the section anchor to the section tree to facilitate this. This adds the ability to generate TOCs in extensions; haven't converted Parser.php to use it (yet?). As a side effect, this fixes API bug 18720

RELEASE-NOTES
includes/Linker.php
includes/parser/Parser.php

index 493e130..06fdbc1 100644 (file)
@@ -237,6 +237,7 @@ it from source control: http://www.mediawiki.org/wiki/Download_from_SVN
 * (bug 19313) action=rollback returns wrong revid on master/slave setups
 * (bug 19323) action=parse doesn't return section tree on pages with Cite
   warnings
+* (bug 18720) Add anchor field to action=parse&prop=sections output
 
 === Languages updated in 1.16 ===
 
index 0f97198..48339e1 100644 (file)
@@ -1176,6 +1176,32 @@ class Linker {
                 . ' } '
                 . "</script>\n";
        }
+       
+       /**
+        * Generate a table of contents from a section tree
+        * @param $tree Return value of ParserOutput::getSections()
+        * @return string HTML
+        */
+       public function generateTOC( $tree ) {
+               $toc = '';
+               $lastLevel = 0;
+               foreach ( $tree as $section ) {
+                       if ( $section['toclevel'] > $lastLevel )
+                               $toc .= $this->tocIndent();
+                       else if ( $secton['toclevel'] < $lastLevel )
+                               $toc .= $this->tocUnindent(
+                                       $lastLevel - $section['toclevel'] );
+                       else
+                               $toc .= $this->tocLineEnd();
+                       
+                       $toc .= $this->tocLine( $section['anchor'],
+                               $section['line'], $section['number'],
+                               $section['toclevel'], $section['index'] );
+                       $lastLevel = $section['toclevel'];
+               }
+               $toc .= $this->tocLineEnd();
+               return $this->tocList( $toc );
+       }
 
        /**
         * Create a section edit link.  This supersedes editSectionLink() and
index 82c6af8..68ca150 100644 (file)
@@ -3489,64 +3489,61 @@ class Parser
                        }
                        $level = $matches[1][$headlineCount];
 
-                       if( $doNumberHeadings || $enoughToc ) {
-
-                               if ( $level > $prevlevel ) {
-                                       # Increase TOC level
-                                       $toclevel++;
-                                       $sublevelCount[$toclevel] = 0;
-                                       if( $toclevel<$wgMaxTocLevel ) {
-                                               $prevtoclevel = $toclevel;
-                                               $toc .= $sk->tocIndent();
-                                               $numVisible++;
-                                       }
+                       if ( $level > $prevlevel ) {
+                               # Increase TOC level
+                               $toclevel++;
+                               $sublevelCount[$toclevel] = 0;
+                               if( $toclevel<$wgMaxTocLevel ) {
+                                       $prevtoclevel = $toclevel;
+                                       $toc .= $sk->tocIndent();
+                                       $numVisible++;
                                }
-                               elseif ( $level < $prevlevel && $toclevel > 1 ) {
-                                       # Decrease TOC level, find level to jump to
+                       }
+                       elseif ( $level < $prevlevel && $toclevel > 1 ) {
+                               # Decrease TOC level, find level to jump to
 
-                                       for ($i = $toclevel; $i > 0; $i--) {
-                                               if ( $levelCount[$i] == $level ) {
-                                                       # Found last matching level
-                                                       $toclevel = $i;
-                                                       break;
-                                               }
-                                               elseif ( $levelCount[$i] < $level ) {
-                                                       # Found first matching level below current level
-                                                       $toclevel = $i + 1;
-                                                       break;
-                                               }
+                               for ($i = $toclevel; $i > 0; $i--) {
+                                       if ( $levelCount[$i] == $level ) {
+                                               # Found last matching level
+                                               $toclevel = $i;
+                                               break;
                                        }
-                                       if( $i == 0 ) $toclevel = 1;
-                                       if( $toclevel<$wgMaxTocLevel ) {
-                                               if($prevtoclevel < $wgMaxTocLevel) {
-                                                       # Unindent only if the previous toc level was shown :p
-                                                       $toc .= $sk->tocUnindent( $prevtoclevel - $toclevel );
-                                                       $prevtoclevel = $toclevel;
-                                               } else {
-                                                       $toc .= $sk->tocLineEnd();
-                                               }
+                                       elseif ( $levelCount[$i] < $level ) {
+                                               # Found first matching level below current level
+                                               $toclevel = $i + 1;
+                                               break;
                                        }
                                }
-                               else {
-                                       # No change in level, end TOC line
-                                       if( $toclevel<$wgMaxTocLevel ) {
+                               if( $i == 0 ) $toclevel = 1;
+                               if( $toclevel<$wgMaxTocLevel ) {
+                                       if($prevtoclevel < $wgMaxTocLevel) {
+                                               # Unindent only if the previous toc level was shown :p
+                                               $toc .= $sk->tocUnindent( $prevtoclevel - $toclevel );
+                                               $prevtoclevel = $toclevel;
+                                       } else {
                                                $toc .= $sk->tocLineEnd();
                                        }
                                }
+                       }
+                       else {
+                               # No change in level, end TOC line
+                               if( $toclevel<$wgMaxTocLevel ) {
+                                       $toc .= $sk->tocLineEnd();
+                               }
+                       }
 
-                               $levelCount[$toclevel] = $level;
+                       $levelCount[$toclevel] = $level;
 
-                               # count number of headlines for each level
-                               @$sublevelCount[$toclevel]++;
-                               $dot = 0;
-                               for( $i = 1; $i <= $toclevel; $i++ ) {
-                                       if( !empty( $sublevelCount[$i] ) ) {
-                                               if( $dot ) {
-                                                       $numbering .= '.';
-                                               }
-                                               $numbering .= $wgContLang->formatNum( $sublevelCount[$i] );
-                                               $dot = 1;
+                       # count number of headlines for each level
+                       @$sublevelCount[$toclevel]++;
+                       $dot = 0;
+                       for( $i = 1; $i <= $toclevel; $i++ ) {
+                               if( !empty( $sublevelCount[$i] ) ) {
+                                       if( $dot ) {
+                                               $numbering .= '.';
                                        }
+                                       $numbering .= $wgContLang->formatNum( $sublevelCount[$i] );
+                                       $dot = 1;
                                }
                        }
 
@@ -3648,28 +3645,31 @@ class Parser
                        if( $enoughToc && ( !isset($wgMaxTocLevel) || $toclevel<$wgMaxTocLevel ) ) {
                                $toc .= $sk->tocLine($anchor, $tocline,
                                        $numbering, $toclevel, ($isTemplate ? false : $sectionIndex));
-                               
-                               # Find the DOM node for this header
-                               while ( $node && !$isTemplate ) {
-                                       if ( $node->getName() === 'h' ) {
-                                               $bits = $node->splitHeading();
-                                               if ( $bits['i'] == $sectionIndex )
-                                                       break;
-                                       }
-                                       $byteOffset += mb_strlen( $this->mStripState->unstripBoth( 
-                                               $frame->expand( $node, PPFrame::RECOVER_ORIG ) ) );
-                                       $node = $node->getNextSibling();
+                       }
+                       
+                       # Add the section to the section tree
+                       # Find the DOM node for this header
+                       while ( $node && !$isTemplate ) {
+                               if ( $node->getName() === 'h' ) {
+                                       $bits = $node->splitHeading();
+                                       if ( $bits['i'] == $sectionIndex )
+                                               break;
                                }
-                               $tocraw[] = array( 
-                                       'toclevel' => $toclevel,
-                                       'level' => $level,
-                                       'line' => $tocline,
-                                       'number' => $numbering,
-                                       'index' => $sectionIndex,
-                                       'fromtitle' => $titleText,
-                                       'byteoffset' => ( $isTemplate ? null : $byteOffset ),
-                               );
+                               $byteOffset += mb_strlen( $this->mStripState->unstripBoth( 
+                                       $frame->expand( $node, PPFrame::RECOVER_ORIG ) ) );
+                               $node = $node->getNextSibling();
                        }
+                       $tocraw[] = array( 
+                               'toclevel' => $toclevel,
+                               'level' => $level,
+                               'line' => $tocline,
+                               'number' => $numbering,
+                               'index' => ($isTemplate ? 'T-' : '' ) . $sectionIndex,
+                               'fromtitle' => $titleText,
+                               'byteoffset' => ( $isTemplate ? null : $byteOffset ),
+                               'anchor' => $anchor,
+                       );
+                       
                        # give headline the correct <h#> tag
                        if( $showEditLink && $sectionIndex !== false ) {
                                if( $isTemplate ) {
@@ -3740,6 +3740,96 @@ class Parser
                }
        }
        
+       /**
+        * Merge $tree2 into $tree1 by replacing the section with index
+        * $section in $tree1 and its descendants with the sections in $tree2.
+        * Note that in the returned section tree, only the 'index' and
+        * 'byteoffset' fields are guaranteed to be correct.
+        * @param $tree1 array Section tree from ParserOutput::getSectons()
+       *  @param $tree2 array Section tree
+        * @param $section int Section index
+        * @param $title Title Title both section trees come from
+        * @param $len2 int Length of the original wikitext for $tree2
+        * @return array Merged section tree
+        */
+       public static function mergeSectionTrees( $tree1, $tree2, $section, $title, $len2 ) {
+               global $wgContLang;
+               $newTree = array();
+               $targetLevel = false;
+               $merged = false;
+               $lastLevel = 1;
+               $nextIndex = 1;
+               $numbering = array( 0 );
+               $titletext = $title->getPrefixedDBkey();
+               foreach ( $tree1 as $s ) {
+                       if ( $targetLevel !== false ) { 
+                               if ( $s['level'] <= $targetLevel )
+                                       // We've skipped enough
+                                       $targetLevel = false;
+                               else
+                                       continue;
+                       }
+                       if ( $s['index'] != $section ||
+                                       $s['fromtitle'] != $titletext ) {
+                               self::incrementNumbering( $numbering,
+                                       $s['toclevel'], $lastLevel );
+                               
+                               // Rewrite index, byteoffset and number
+                               if ( $s['fromtitle'] == $titletext ) {
+                                       $s['index'] = $nextIndex++;
+                                       if ( $merged )
+                                               $s['byteoffset'] += $len2;
+                               }
+                               $s['number']  = implode( '.', array_map(
+                                       array( $wgContLang, 'formatnum' ),
+                                       $numbering ) );
+                               $lastLevel = $s['toclevel'];
+                               $newTree[] = $s;
+                       } else {
+                               // We're at $section
+                               // Insert sections from $tree2 here
+                               foreach ( $tree2 as $s2 ) {
+                                       // Rewrite the fields in $s2
+                                       // before inserting it
+                                       $s2['toclevel'] += $s['toclevel'] - 1;
+                                       $s2['level'] += $s['level'] - 1;
+                                       $s2['index'] = $nextIndex++;
+                                       $s2['byteoffset'] += $s['byteoffset'];
+                                       
+                                       self::incrementNumbering( $numbering,
+                                               $s2['toclevel'], $lastLevel );
+                                       $s2['number']  = implode( '.', array_map(
+                                               array( $wgContLang, 'formatnum' ),
+                                               $numbering ) );
+                                       $lastLevel = $s2['toclevel'];
+                                       $newTree[] = $s2;
+                               }
+                               // Skip all descendants of $section in $tree1
+                               $targetLevel = $s['level'];
+                               $merged = true;
+                       }
+               }
+               return $newTree;
+       }
+       
+       /**
+        * Increment a section number. Helper function for mergeSectionTrees()
+        * @param $number array Array representing a section number
+        * @param $level int Current TOC level (depth)
+        * @param $lastLevel int Level of previous TOC entry
+        */
+       private static function incrementNumbering( &$number, $level, $lastLevel ) {
+               if ( $level > $lastLevel )
+                       $number[$level - 1] = 1;
+               else if ( $level < $lastLevel ) {
+                       foreach ( $number as $key => $unused )
+                               if ( $key >= $level )
+                                       unset( $number[$key] );
+                       $number[$level - 1]++;
+               } else
+                       $number[$level - 1]++;
+       }
+       
        /**
         * Transform wiki markup when saving a page by doing \r\n -> \n
         * conversion, substitting signatures, {{subst:}} templates, etc.