From 82022f21bc2f459307f679436cbff30b4cad36d2 Mon Sep 17 00:00:00 2001 From: Roan Kattouw Date: Thu, 25 Jun 2009 11:05:22 +0000 Subject: [PATCH] Core changes for NavigableTOC extension: * 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 | 1 + includes/Linker.php | 26 +++++ includes/parser/Parser.php | 224 ++++++++++++++++++++++++++----------- 3 files changed, 184 insertions(+), 67 deletions(-) diff --git a/RELEASE-NOTES b/RELEASE-NOTES index 493e1303e3..06fdbc1905 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -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 === diff --git a/includes/Linker.php b/includes/Linker.php index 0f97198cb7..48339e14a0 100644 --- a/includes/Linker.php +++ b/includes/Linker.php @@ -1176,6 +1176,32 @@ class Linker { . ' } ' . "\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 diff --git a/includes/parser/Parser.php b/includes/parser/Parser.php index 82c6af8b09..68ca150b26 100644 --- a/includes/parser/Parser.php +++ b/includes/parser/Parser.php @@ -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 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. -- 2.20.1