2008-02-05 08:23:58 +00:00
|
|
|
<?php
|
2010-08-22 14:31:05 +00:00
|
|
|
/**
|
|
|
|
|
* Preprocessor using PHP arrays
|
|
|
|
|
*
|
2012-04-30 09:22:16 +00:00
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
|
* (at your option) any later version.
|
|
|
|
|
*
|
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU General Public License along
|
|
|
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
|
* http://www.gnu.org/copyleft/gpl.html
|
|
|
|
|
*
|
2010-08-22 14:31:05 +00:00
|
|
|
* @file
|
|
|
|
|
* @ingroup Parser
|
|
|
|
|
*/
|
2010-12-11 03:52:35 +00:00
|
|
|
|
2008-02-05 08:23:58 +00:00
|
|
|
/**
|
|
|
|
|
* Differences from DOM schema:
|
|
|
|
|
* * attribute nodes are children
|
2012-07-10 12:48:06 +00:00
|
|
|
* * "<h>" nodes that aren't at the top are replaced with <possible-h>
|
WARNING: HUGE COMMIT
Doxygen documentation update:
* Changed alls @addtogroup to @ingroup. @addtogroup adds the comment to the group description, but doesn't add the file, class, function, ... to the group like @ingroup does. See for example http://svn.wikimedia.org/doc/group__SpecialPage.html where it's impossible to see related files, classes, ... that should belong to that group.
* Added @file to file description, it seems that it should be explicitely decalred for file descriptions, otherwise doxygen will think that the comment document the first class, variabled, function, ... that is in that file.
* Removed some empty comments
* Removed some ?>
Added following groups:
* ExternalStorage
* JobQueue
* MaintenanceLanguage
One more thing: there are still a lot of warnings when generating the doc.
2008-05-20 17:13:28 +00:00
|
|
|
* @ingroup Parser
|
2008-02-05 08:23:58 +00:00
|
|
|
*/
|
|
|
|
|
class Preprocessor_Hash implements Preprocessor {
|
2011-02-19 01:02:56 +00:00
|
|
|
/**
|
|
|
|
|
* @var Parser
|
|
|
|
|
*/
|
2008-02-05 08:23:58 +00:00
|
|
|
var $parser;
|
2010-12-11 03:52:35 +00:00
|
|
|
|
2009-02-09 23:18:37 +00:00
|
|
|
const CACHE_VERSION = 1;
|
2008-02-05 08:23:58 +00:00
|
|
|
|
|
|
|
|
function __construct( $parser ) {
|
|
|
|
|
$this->parser = $parser;
|
|
|
|
|
}
|
|
|
|
|
|
2011-05-26 19:52:56 +00:00
|
|
|
/**
|
|
|
|
|
* @return PPFrame_Hash
|
|
|
|
|
*/
|
2008-02-05 08:23:58 +00:00
|
|
|
function newFrame() {
|
|
|
|
|
return new PPFrame_Hash( $this );
|
|
|
|
|
}
|
|
|
|
|
|
2011-05-26 19:52:56 +00:00
|
|
|
/**
|
2012-05-11 19:17:39 +00:00
|
|
|
* @param $args array
|
2011-05-26 19:52:56 +00:00
|
|
|
* @return PPCustomFrame_Hash
|
|
|
|
|
*/
|
2008-06-26 13:05:40 +00:00
|
|
|
function newCustomFrame( $args ) {
|
|
|
|
|
return new PPCustomFrame_Hash( $this, $args );
|
|
|
|
|
}
|
|
|
|
|
|
2011-05-29 14:01:47 +00:00
|
|
|
/**
|
|
|
|
|
* @param $values array
|
|
|
|
|
* @return PPNode_Hash_Array
|
|
|
|
|
*/
|
2010-06-10 15:16:15 +00:00
|
|
|
function newPartNodeArray( $values ) {
|
|
|
|
|
$list = array();
|
|
|
|
|
|
|
|
|
|
foreach ( $values as $k => $val ) {
|
|
|
|
|
$partNode = new PPNode_Hash_Tree( 'part' );
|
|
|
|
|
$nameNode = new PPNode_Hash_Tree( 'name' );
|
|
|
|
|
|
|
|
|
|
if ( is_int( $k ) ) {
|
|
|
|
|
$nameNode->addChild( new PPNode_Hash_Attr( 'index', $k ) );
|
|
|
|
|
$partNode->addChild( $nameNode );
|
|
|
|
|
} else {
|
|
|
|
|
$nameNode->addChild( new PPNode_Hash_Text( $k ) );
|
|
|
|
|
$partNode->addChild( $nameNode );
|
|
|
|
|
$partNode->addChild( new PPNode_Hash_Text( '=' ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$valueNode = new PPNode_Hash_Tree( 'value' );
|
|
|
|
|
$valueNode->addChild( new PPNode_Hash_Text( $val ) );
|
|
|
|
|
$partNode->addChild( $valueNode );
|
|
|
|
|
|
|
|
|
|
$list[] = $partNode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$node = new PPNode_Hash_Array( $list );
|
|
|
|
|
return $node;
|
|
|
|
|
}
|
|
|
|
|
|
2008-02-05 08:23:58 +00:00
|
|
|
/**
|
|
|
|
|
* Preprocess some wikitext and return the document tree.
|
2008-04-14 07:45:50 +00:00
|
|
|
* This is the ghost of Parser::replace_variables().
|
2008-02-05 08:23:58 +00:00
|
|
|
*
|
2010-06-09 14:57:59 +00:00
|
|
|
* @param $text String: the text to parse
|
|
|
|
|
* @param $flags Integer: bitwise combination of:
|
2012-07-10 12:48:06 +00:00
|
|
|
* Parser::PTD_FOR_INCLUSION Handle "<noinclude>" and "<includeonly>" as if the text is being
|
2008-04-14 07:45:50 +00:00
|
|
|
* included. Default is to assume a direct page view.
|
2008-02-05 08:23:58 +00:00
|
|
|
*
|
|
|
|
|
* The generated DOM tree must depend only on the input text and the flags.
|
2008-04-14 07:45:50 +00:00
|
|
|
* The DOM tree must be the same in OT_HTML and OT_WIKI mode, to avoid a regression of bug 4899.
|
2008-02-05 08:23:58 +00:00
|
|
|
*
|
2008-04-14 07:45:50 +00:00
|
|
|
* Any flag added to the $flags parameter here, or any other parameter liable to cause a
|
|
|
|
|
* change in the DOM tree for a given text, must be passed through the section identifier
|
|
|
|
|
* in the section edit link and thus back to extractSections().
|
2008-02-05 08:23:58 +00:00
|
|
|
*
|
2008-04-14 07:45:50 +00:00
|
|
|
* The output of this function is currently only cached in process memory, but a persistent
|
|
|
|
|
* cache may be implemented at a later date which takes further advantage of these strict
|
2008-02-05 08:23:58 +00:00
|
|
|
* dependency requirements.
|
|
|
|
|
*
|
2012-10-07 23:35:26 +00:00
|
|
|
* @throws MWException
|
2011-05-29 14:01:47 +00:00
|
|
|
* @return PPNode_Hash_Tree
|
2008-02-05 08:23:58 +00:00
|
|
|
*/
|
|
|
|
|
function preprocessToObj( $text, $flags = 0 ) {
|
|
|
|
|
wfProfileIn( __METHOD__ );
|
2010-12-11 03:52:35 +00:00
|
|
|
|
2009-02-09 23:18:37 +00:00
|
|
|
// Check cache.
|
|
|
|
|
global $wgMemc, $wgPreprocessorCacheThreshold;
|
2010-12-11 03:52:35 +00:00
|
|
|
|
2011-01-13 17:35:54 +00:00
|
|
|
$cacheable = $wgPreprocessorCacheThreshold !== false && strlen( $text ) > $wgPreprocessorCacheThreshold;
|
2009-02-09 23:18:37 +00:00
|
|
|
if ( $cacheable ) {
|
2013-02-03 19:42:08 +00:00
|
|
|
wfProfileIn( __METHOD__ . '-cacheable' );
|
2009-02-09 23:18:37 +00:00
|
|
|
|
2013-02-03 19:42:08 +00:00
|
|
|
$cacheKey = wfMemcKey( 'preprocess-hash', md5( $text ), $flags );
|
2009-02-09 23:18:37 +00:00
|
|
|
$cacheValue = $wgMemc->get( $cacheKey );
|
|
|
|
|
if ( $cacheValue ) {
|
|
|
|
|
$version = substr( $cacheValue, 0, 8 );
|
|
|
|
|
if ( intval( $version ) == self::CACHE_VERSION ) {
|
|
|
|
|
$hash = unserialize( substr( $cacheValue, 8 ) );
|
|
|
|
|
// From the cache
|
|
|
|
|
wfDebugLog( "Preprocessor",
|
|
|
|
|
"Loaded preprocessor hash from memcached (key $cacheKey)" );
|
2013-02-03 19:42:08 +00:00
|
|
|
wfProfileOut( __METHOD__ . '-cacheable' );
|
2009-02-09 23:18:37 +00:00
|
|
|
wfProfileOut( __METHOD__ );
|
|
|
|
|
return $hash;
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-02-03 19:42:08 +00:00
|
|
|
wfProfileIn( __METHOD__ . '-cache-miss' );
|
2009-02-06 20:27:58 +00:00
|
|
|
}
|
2008-02-05 08:23:58 +00:00
|
|
|
|
|
|
|
|
$rules = array(
|
|
|
|
|
'{' => array(
|
|
|
|
|
'end' => '}',
|
|
|
|
|
'names' => array(
|
|
|
|
|
2 => 'template',
|
|
|
|
|
3 => 'tplarg',
|
|
|
|
|
),
|
|
|
|
|
'min' => 2,
|
|
|
|
|
'max' => 3,
|
|
|
|
|
),
|
|
|
|
|
'[' => array(
|
|
|
|
|
'end' => ']',
|
|
|
|
|
'names' => array( 2 => null ),
|
|
|
|
|
'min' => 2,
|
|
|
|
|
'max' => 2,
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$forInclusion = $flags & Parser::PTD_FOR_INCLUSION;
|
|
|
|
|
|
|
|
|
|
$xmlishElements = $this->parser->getStripList();
|
|
|
|
|
$enableOnlyinclude = false;
|
|
|
|
|
if ( $forInclusion ) {
|
|
|
|
|
$ignoredTags = array( 'includeonly', '/includeonly' );
|
|
|
|
|
$ignoredElements = array( 'noinclude' );
|
|
|
|
|
$xmlishElements[] = 'noinclude';
|
|
|
|
|
if ( strpos( $text, '<onlyinclude>' ) !== false && strpos( $text, '</onlyinclude>' ) !== false ) {
|
|
|
|
|
$enableOnlyinclude = true;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
$ignoredTags = array( 'noinclude', '/noinclude', 'onlyinclude', '/onlyinclude' );
|
|
|
|
|
$ignoredElements = array( 'includeonly' );
|
|
|
|
|
$xmlishElements[] = 'includeonly';
|
|
|
|
|
}
|
|
|
|
|
$xmlishRegex = implode( '|', array_merge( $xmlishElements, $ignoredTags ) );
|
|
|
|
|
|
|
|
|
|
// Use "A" modifier (anchored) instead of "^", because ^ doesn't work with an offset
|
|
|
|
|
$elementsRegex = "~($xmlishRegex)(?:\s|\/>|>)|(!--)~iA";
|
2008-04-14 07:45:50 +00:00
|
|
|
|
2008-02-05 08:23:58 +00:00
|
|
|
$stack = new PPDStack_Hash;
|
|
|
|
|
|
|
|
|
|
$searchBase = "[{<\n";
|
|
|
|
|
$revText = strrev( $text ); // For fast reverse searches
|
2012-08-06 11:41:28 +00:00
|
|
|
$lengthText = strlen( $text );
|
2008-02-05 08:23:58 +00:00
|
|
|
|
|
|
|
|
$i = 0; # Input pointer, starts out pointing to a pseudo-newline before the start
|
|
|
|
|
$accum =& $stack->getAccum(); # Current accumulator
|
|
|
|
|
$findEquals = false; # True to find equals signs in arguments
|
|
|
|
|
$findPipe = false; # True to take notice of pipe characters
|
|
|
|
|
$headingIndex = 1;
|
|
|
|
|
$inHeading = false; # True if $i is inside a possible heading
|
|
|
|
|
$noMoreGT = false; # True if there are no more greater-than (>) signs right of $i
|
|
|
|
|
$findOnlyinclude = $enableOnlyinclude; # True to ignore all input up to the next <onlyinclude>
|
|
|
|
|
$fakeLineStart = true; # Do a line-start run without outputting an LF character
|
|
|
|
|
|
|
|
|
|
while ( true ) {
|
|
|
|
|
//$this->memCheck();
|
|
|
|
|
|
|
|
|
|
if ( $findOnlyinclude ) {
|
|
|
|
|
// Ignore all input up to the next <onlyinclude>
|
|
|
|
|
$startPos = strpos( $text, '<onlyinclude>', $i );
|
|
|
|
|
if ( $startPos === false ) {
|
|
|
|
|
// Ignored section runs to the end
|
|
|
|
|
$accum->addNodeWithText( 'ignore', substr( $text, $i ) );
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
$tagEndPos = $startPos + strlen( '<onlyinclude>' ); // past-the-end
|
|
|
|
|
$accum->addNodeWithText( 'ignore', substr( $text, $i, $tagEndPos - $i ) );
|
|
|
|
|
$i = $tagEndPos;
|
|
|
|
|
$findOnlyinclude = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $fakeLineStart ) {
|
|
|
|
|
$found = 'line-start';
|
|
|
|
|
$curChar = '';
|
|
|
|
|
} else {
|
|
|
|
|
# Find next opening brace, closing brace or pipe
|
|
|
|
|
$search = $searchBase;
|
|
|
|
|
if ( $stack->top === false ) {
|
|
|
|
|
$currentClosing = '';
|
|
|
|
|
} else {
|
|
|
|
|
$currentClosing = $stack->top->close;
|
|
|
|
|
$search .= $currentClosing;
|
|
|
|
|
}
|
|
|
|
|
if ( $findPipe ) {
|
|
|
|
|
$search .= '|';
|
|
|
|
|
}
|
|
|
|
|
if ( $findEquals ) {
|
|
|
|
|
// First equals will be for the template
|
|
|
|
|
$search .= '=';
|
|
|
|
|
}
|
|
|
|
|
$rule = null;
|
|
|
|
|
# Output literal section, advance input counter
|
|
|
|
|
$literalLength = strcspn( $text, $search, $i );
|
|
|
|
|
if ( $literalLength > 0 ) {
|
|
|
|
|
$accum->addLiteral( substr( $text, $i, $literalLength ) );
|
|
|
|
|
$i += $literalLength;
|
|
|
|
|
}
|
2012-08-06 11:41:28 +00:00
|
|
|
if ( $i >= $lengthText ) {
|
2008-02-05 08:23:58 +00:00
|
|
|
if ( $currentClosing == "\n" ) {
|
|
|
|
|
// Do a past-the-end run to finish off the heading
|
|
|
|
|
$curChar = '';
|
|
|
|
|
$found = 'line-end';
|
|
|
|
|
} else {
|
|
|
|
|
# All done
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
$curChar = $text[$i];
|
|
|
|
|
if ( $curChar == '|' ) {
|
|
|
|
|
$found = 'pipe';
|
|
|
|
|
} elseif ( $curChar == '=' ) {
|
|
|
|
|
$found = 'equals';
|
|
|
|
|
} elseif ( $curChar == '<' ) {
|
|
|
|
|
$found = 'angle';
|
|
|
|
|
} elseif ( $curChar == "\n" ) {
|
|
|
|
|
if ( $inHeading ) {
|
|
|
|
|
$found = 'line-end';
|
|
|
|
|
} else {
|
|
|
|
|
$found = 'line-start';
|
|
|
|
|
}
|
|
|
|
|
} elseif ( $curChar == $currentClosing ) {
|
|
|
|
|
$found = 'close';
|
|
|
|
|
} elseif ( isset( $rules[$curChar] ) ) {
|
|
|
|
|
$found = 'open';
|
|
|
|
|
$rule = $rules[$curChar];
|
|
|
|
|
} else {
|
|
|
|
|
# Some versions of PHP have a strcspn which stops on null characters
|
|
|
|
|
# Ignore and continue
|
|
|
|
|
++$i;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $found == 'angle' ) {
|
|
|
|
|
$matches = false;
|
|
|
|
|
// Handle </onlyinclude>
|
|
|
|
|
if ( $enableOnlyinclude && substr( $text, $i, strlen( '</onlyinclude>' ) ) == '</onlyinclude>' ) {
|
|
|
|
|
$findOnlyinclude = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Determine element name
|
|
|
|
|
if ( !preg_match( $elementsRegex, $text, $matches, 0, $i + 1 ) ) {
|
|
|
|
|
// Element name missing or not listed
|
|
|
|
|
$accum->addLiteral( '<' );
|
|
|
|
|
++$i;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
// Handle comments
|
|
|
|
|
if ( isset( $matches[2] ) && $matches[2] == '!--' ) {
|
|
|
|
|
// To avoid leaving blank lines, when a comment is both preceded
|
|
|
|
|
// and followed by a newline (ignoring spaces), trim leading and
|
|
|
|
|
// trailing spaces and one of the newlines.
|
2008-04-14 07:45:50 +00:00
|
|
|
|
2008-02-05 08:23:58 +00:00
|
|
|
// Find the end
|
|
|
|
|
$endPos = strpos( $text, '-->', $i + 4 );
|
|
|
|
|
if ( $endPos === false ) {
|
|
|
|
|
// Unclosed comment in input, runs to end
|
|
|
|
|
$inner = substr( $text, $i );
|
|
|
|
|
$accum->addNodeWithText( 'comment', $inner );
|
2012-08-06 11:41:28 +00:00
|
|
|
$i = $lengthText;
|
2008-02-05 08:23:58 +00:00
|
|
|
} else {
|
|
|
|
|
// Search backwards for leading whitespace
|
2012-08-06 11:41:28 +00:00
|
|
|
$wsStart = $i ? ( $i - strspn( $revText, ' ', $lengthText - $i ) ) : 0;
|
2008-02-05 08:23:58 +00:00
|
|
|
// Search forwards for trailing whitespace
|
2011-01-13 17:30:27 +00:00
|
|
|
// $wsEnd will be the position of the last space (or the '>' if there's none)
|
2008-02-05 08:23:58 +00:00
|
|
|
$wsEnd = $endPos + 2 + strspn( $text, ' ', $endPos + 3 );
|
|
|
|
|
// Eat the line if possible
|
2008-04-14 07:45:50 +00:00
|
|
|
// TODO: This could theoretically be done if $wsStart == 0, i.e. for comments at
|
|
|
|
|
// the overall start. That's not how Sanitizer::removeHTMLcomments() did it, but
|
2008-02-05 08:23:58 +00:00
|
|
|
// it's a possible beneficial b/c break.
|
2008-04-14 07:45:50 +00:00
|
|
|
if ( $wsStart > 0 && substr( $text, $wsStart - 1, 1 ) == "\n"
|
2008-02-05 08:23:58 +00:00
|
|
|
&& substr( $text, $wsEnd + 1, 1 ) == "\n" )
|
|
|
|
|
{
|
|
|
|
|
$startPos = $wsStart;
|
|
|
|
|
$endPos = $wsEnd + 1;
|
|
|
|
|
// Remove leading whitespace from the end of the accumulator
|
|
|
|
|
// Sanity check first though
|
|
|
|
|
$wsLength = $i - $wsStart;
|
2008-04-14 07:45:50 +00:00
|
|
|
if ( $wsLength > 0
|
2008-02-05 08:23:58 +00:00
|
|
|
&& $accum->lastNode instanceof PPNode_Hash_Text
|
2008-04-14 07:45:50 +00:00
|
|
|
&& substr( $accum->lastNode->value, -$wsLength ) === str_repeat( ' ', $wsLength ) )
|
2008-02-05 08:23:58 +00:00
|
|
|
{
|
|
|
|
|
$accum->lastNode->value = substr( $accum->lastNode->value, 0, -$wsLength );
|
|
|
|
|
}
|
|
|
|
|
// Do a line-start run next time to look for headings after the comment
|
|
|
|
|
$fakeLineStart = true;
|
|
|
|
|
} else {
|
|
|
|
|
// No line to eat, just take the comment itself
|
|
|
|
|
$startPos = $i;
|
|
|
|
|
$endPos += 2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $stack->top ) {
|
|
|
|
|
$part = $stack->top->getCurrentPart();
|
2013-02-03 19:42:08 +00:00
|
|
|
if ( !(isset( $part->commentEnd ) && $part->commentEnd == $wsStart - 1 )) {
|
2008-02-05 08:23:58 +00:00
|
|
|
$part->visualEnd = $wsStart;
|
|
|
|
|
}
|
2011-04-22 14:25:17 +00:00
|
|
|
// Else comments abutting, no change in visual end
|
|
|
|
|
$part->commentEnd = $endPos;
|
2008-02-05 08:23:58 +00:00
|
|
|
}
|
|
|
|
|
$i = $endPos + 1;
|
|
|
|
|
$inner = substr( $text, $startPos, $endPos - $startPos + 1 );
|
|
|
|
|
$accum->addNodeWithText( 'comment', $inner );
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
$name = $matches[1];
|
2008-03-05 01:07:47 +00:00
|
|
|
$lowerName = strtolower( $name );
|
2008-02-05 08:23:58 +00:00
|
|
|
$attrStart = $i + strlen( $name ) + 1;
|
|
|
|
|
|
|
|
|
|
// Find end of tag
|
|
|
|
|
$tagEndPos = $noMoreGT ? false : strpos( $text, '>', $attrStart );
|
|
|
|
|
if ( $tagEndPos === false ) {
|
|
|
|
|
// Infinite backtrack
|
|
|
|
|
// Disable tag search to prevent worst-case O(N^2) performance
|
|
|
|
|
$noMoreGT = true;
|
|
|
|
|
$accum->addLiteral( '<' );
|
|
|
|
|
++$i;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle ignored tags
|
2008-03-05 01:07:47 +00:00
|
|
|
if ( in_array( $lowerName, $ignoredTags ) ) {
|
2008-02-05 08:23:58 +00:00
|
|
|
$accum->addNodeWithText( 'ignore', substr( $text, $i, $tagEndPos - $i + 1 ) );
|
|
|
|
|
$i = $tagEndPos + 1;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$tagStartPos = $i;
|
|
|
|
|
if ( $text[$tagEndPos-1] == '/' ) {
|
|
|
|
|
// Short end tag
|
|
|
|
|
$attrEnd = $tagEndPos - 1;
|
|
|
|
|
$inner = null;
|
|
|
|
|
$i = $tagEndPos + 1;
|
|
|
|
|
$close = null;
|
|
|
|
|
} else {
|
|
|
|
|
$attrEnd = $tagEndPos;
|
|
|
|
|
// Find closing tag
|
2010-12-11 03:52:35 +00:00
|
|
|
if ( preg_match( "/<\/" . preg_quote( $name, '/' ) . "\s*>/i",
|
|
|
|
|
$text, $matches, PREG_OFFSET_CAPTURE, $tagEndPos + 1 ) )
|
2009-02-04 09:10:32 +00:00
|
|
|
{
|
2008-02-05 08:23:58 +00:00
|
|
|
$inner = substr( $text, $tagEndPos + 1, $matches[0][1] - $tagEndPos - 1 );
|
|
|
|
|
$i = $matches[0][1] + strlen( $matches[0][0] );
|
|
|
|
|
$close = $matches[0][0];
|
|
|
|
|
} else {
|
|
|
|
|
// No end tag -- let it run out to the end of the text.
|
|
|
|
|
$inner = substr( $text, $tagEndPos + 1 );
|
2012-08-06 11:41:28 +00:00
|
|
|
$i = $lengthText;
|
2008-02-05 08:23:58 +00:00
|
|
|
$close = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// <includeonly> and <noinclude> just become <ignore> tags
|
2008-03-05 01:07:47 +00:00
|
|
|
if ( in_array( $lowerName, $ignoredElements ) ) {
|
2013-02-03 19:42:08 +00:00
|
|
|
$accum->addNodeWithText( 'ignore', substr( $text, $tagStartPos, $i - $tagStartPos ) );
|
2008-02-05 08:23:58 +00:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $attrEnd <= $attrStart ) {
|
|
|
|
|
$attr = '';
|
|
|
|
|
} else {
|
2008-04-14 07:45:50 +00:00
|
|
|
// Note that the attr element contains the whitespace between name and attribute,
|
2008-02-05 08:23:58 +00:00
|
|
|
// this is necessary for precise reconstruction during pre-save transform.
|
|
|
|
|
$attr = substr( $text, $attrStart, $attrEnd - $attrStart );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$extNode = new PPNode_Hash_Tree( 'ext' );
|
|
|
|
|
$extNode->addChild( PPNode_Hash_Tree::newWithText( 'name', $name ) );
|
|
|
|
|
$extNode->addChild( PPNode_Hash_Tree::newWithText( 'attr', $attr ) );
|
|
|
|
|
if ( $inner !== null ) {
|
|
|
|
|
$extNode->addChild( PPNode_Hash_Tree::newWithText( 'inner', $inner ) );
|
|
|
|
|
}
|
|
|
|
|
if ( $close !== null ) {
|
|
|
|
|
$extNode->addChild( PPNode_Hash_Tree::newWithText( 'close', $close ) );
|
|
|
|
|
}
|
|
|
|
|
$accum->addNode( $extNode );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
elseif ( $found == 'line-start' ) {
|
2008-04-14 07:45:50 +00:00
|
|
|
// Is this the start of a heading?
|
2008-02-05 08:23:58 +00:00
|
|
|
// Line break belongs before the heading element in any case
|
|
|
|
|
if ( $fakeLineStart ) {
|
|
|
|
|
$fakeLineStart = false;
|
|
|
|
|
} else {
|
|
|
|
|
$accum->addLiteral( $curChar );
|
|
|
|
|
$i++;
|
|
|
|
|
}
|
2008-04-14 07:45:50 +00:00
|
|
|
|
2008-02-05 08:23:58 +00:00
|
|
|
$count = strspn( $text, '=', $i, 6 );
|
|
|
|
|
if ( $count == 1 && $findEquals ) {
|
|
|
|
|
// DWIM: This looks kind of like a name/value separator
|
|
|
|
|
// Let's let the equals handler have it and break the potential heading
|
|
|
|
|
// This is heuristic, but AFAICT the methods for completely correct disambiguation are very complex.
|
|
|
|
|
} elseif ( $count > 0 ) {
|
|
|
|
|
$piece = array(
|
|
|
|
|
'open' => "\n",
|
|
|
|
|
'close' => "\n",
|
|
|
|
|
'parts' => array( new PPDPart_Hash( str_repeat( '=', $count ) ) ),
|
|
|
|
|
'startPos' => $i,
|
|
|
|
|
'count' => $count );
|
|
|
|
|
$stack->push( $piece );
|
|
|
|
|
$accum =& $stack->getAccum();
|
|
|
|
|
extract( $stack->getFlags() );
|
|
|
|
|
$i += $count;
|
|
|
|
|
}
|
2011-05-29 14:01:47 +00:00
|
|
|
} elseif ( $found == 'line-end' ) {
|
2008-02-05 08:23:58 +00:00
|
|
|
$piece = $stack->top;
|
|
|
|
|
// A heading must be open, otherwise \n wouldn't have been in the search list
|
2012-03-19 11:14:43 +00:00
|
|
|
assert( '$piece->open == "\n"' );
|
2008-02-05 08:23:58 +00:00
|
|
|
$part = $piece->getCurrentPart();
|
|
|
|
|
// Search back through the input to see if it has a proper close
|
|
|
|
|
// Do this using the reversed string since the other solutions (end anchor, etc.) are inefficient
|
2012-08-06 11:41:28 +00:00
|
|
|
$wsLength = strspn( $revText, " \t", $lengthText - $i );
|
2008-02-05 08:23:58 +00:00
|
|
|
$searchStart = $i - $wsLength;
|
|
|
|
|
if ( isset( $part->commentEnd ) && $searchStart - 1 == $part->commentEnd ) {
|
|
|
|
|
// Comment found at line end
|
|
|
|
|
// Search for equals signs before the comment
|
|
|
|
|
$searchStart = $part->visualEnd;
|
2012-08-06 11:41:28 +00:00
|
|
|
$searchStart -= strspn( $revText, " \t", $lengthText - $searchStart );
|
2008-02-05 08:23:58 +00:00
|
|
|
}
|
|
|
|
|
$count = $piece->count;
|
2012-08-06 11:41:28 +00:00
|
|
|
$equalsLength = strspn( $revText, '=', $lengthText - $searchStart );
|
2008-02-05 08:23:58 +00:00
|
|
|
if ( $equalsLength > 0 ) {
|
2010-06-21 20:33:07 +00:00
|
|
|
if ( $searchStart - $equalsLength == $piece->startPos ) {
|
2008-02-05 08:23:58 +00:00
|
|
|
// This is just a single string of equals signs on its own line
|
2013-03-04 08:44:38 +00:00
|
|
|
// Replicate the doHeadings behavior /={count}(.+)={count}/
|
2008-02-05 08:23:58 +00:00
|
|
|
// First find out how many equals signs there really are (don't stop at 6)
|
|
|
|
|
$count = $equalsLength;
|
|
|
|
|
if ( $count < 3 ) {
|
|
|
|
|
$count = 0;
|
|
|
|
|
} else {
|
|
|
|
|
$count = min( 6, intval( ( $count - 1 ) / 2 ) );
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
$count = min( $equalsLength, $count );
|
|
|
|
|
}
|
|
|
|
|
if ( $count > 0 ) {
|
|
|
|
|
// Normal match, output <h>
|
|
|
|
|
$element = new PPNode_Hash_Tree( 'possible-h' );
|
|
|
|
|
$element->addChild( new PPNode_Hash_Attr( 'level', $count ) );
|
|
|
|
|
$element->addChild( new PPNode_Hash_Attr( 'i', $headingIndex++ ) );
|
|
|
|
|
$element->lastChild->nextSibling = $accum->firstNode;
|
|
|
|
|
$element->lastChild = $accum->lastNode;
|
|
|
|
|
} else {
|
|
|
|
|
// Single equals sign on its own line, count=0
|
|
|
|
|
$element = $accum;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// No match, no <h>, just pass down the inner text
|
|
|
|
|
$element = $accum;
|
|
|
|
|
}
|
|
|
|
|
// Unwind the stack
|
|
|
|
|
$stack->pop();
|
|
|
|
|
$accum =& $stack->getAccum();
|
|
|
|
|
extract( $stack->getFlags() );
|
|
|
|
|
|
|
|
|
|
// Append the result to the enclosing accumulator
|
|
|
|
|
if ( $element instanceof PPNode ) {
|
|
|
|
|
$accum->addNode( $element );
|
|
|
|
|
} else {
|
|
|
|
|
$accum->addAccum( $element );
|
|
|
|
|
}
|
|
|
|
|
// Note that we do NOT increment the input pointer.
|
2008-04-14 07:45:50 +00:00
|
|
|
// This is because the closing linebreak could be the opening linebreak of
|
2008-02-05 08:23:58 +00:00
|
|
|
// another heading. Infinite loops are avoided because the next iteration MUST
|
2008-04-14 07:45:50 +00:00
|
|
|
// hit the heading open case above, which unconditionally increments the
|
2008-02-05 08:23:58 +00:00
|
|
|
// input pointer.
|
2011-05-29 14:01:47 +00:00
|
|
|
} elseif ( $found == 'open' ) {
|
2008-02-05 08:23:58 +00:00
|
|
|
# count opening brace characters
|
|
|
|
|
$count = strspn( $text, $curChar, $i );
|
|
|
|
|
|
|
|
|
|
# we need to add to stack only if opening brace count is enough for one of the rules
|
|
|
|
|
if ( $count >= $rule['min'] ) {
|
|
|
|
|
# Add it to the stack
|
|
|
|
|
$piece = array(
|
|
|
|
|
'open' => $curChar,
|
|
|
|
|
'close' => $rule['end'],
|
|
|
|
|
'count' => $count,
|
2011-09-12 19:16:22 +00:00
|
|
|
'lineStart' => ($i > 0 && $text[$i-1] == "\n"),
|
2008-02-05 08:23:58 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$stack->push( $piece );
|
|
|
|
|
$accum =& $stack->getAccum();
|
|
|
|
|
extract( $stack->getFlags() );
|
|
|
|
|
} else {
|
|
|
|
|
# Add literal brace(s)
|
|
|
|
|
$accum->addLiteral( str_repeat( $curChar, $count ) );
|
|
|
|
|
}
|
|
|
|
|
$i += $count;
|
2011-05-29 14:01:47 +00:00
|
|
|
} elseif ( $found == 'close' ) {
|
2008-02-05 08:23:58 +00:00
|
|
|
$piece = $stack->top;
|
|
|
|
|
# lets check if there are enough characters for closing brace
|
|
|
|
|
$maxCount = $piece->count;
|
|
|
|
|
$count = strspn( $text, $curChar, $i, $maxCount );
|
|
|
|
|
|
|
|
|
|
# check for maximum matching characters (if there are 5 closing
|
|
|
|
|
# characters, we will probably need only 3 - depending on the rules)
|
|
|
|
|
$rule = $rules[$piece->open];
|
|
|
|
|
if ( $count > $rule['max'] ) {
|
|
|
|
|
# The specified maximum exists in the callback array, unless the caller
|
|
|
|
|
# has made an error
|
|
|
|
|
$matchingCount = $rule['max'];
|
|
|
|
|
} else {
|
|
|
|
|
# Count is less than the maximum
|
|
|
|
|
# Skip any gaps in the callback array to find the true largest match
|
|
|
|
|
# Need to use array_key_exists not isset because the callback can be null
|
|
|
|
|
$matchingCount = $count;
|
|
|
|
|
while ( $matchingCount > 0 && !array_key_exists( $matchingCount, $rule['names'] ) ) {
|
|
|
|
|
--$matchingCount;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-02-03 19:42:08 +00:00
|
|
|
if ( $matchingCount <= 0 ) {
|
2008-02-05 08:23:58 +00:00
|
|
|
# No matching element found in callback array
|
|
|
|
|
# Output a literal closing brace and continue
|
|
|
|
|
$accum->addLiteral( str_repeat( $curChar, $count ) );
|
|
|
|
|
$i += $count;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
$name = $rule['names'][$matchingCount];
|
|
|
|
|
if ( $name === null ) {
|
|
|
|
|
// No element, just literal text
|
|
|
|
|
$element = $piece->breakSyntax( $matchingCount );
|
|
|
|
|
$element->addLiteral( str_repeat( $rule['end'], $matchingCount ) );
|
|
|
|
|
} else {
|
|
|
|
|
# Create XML element
|
|
|
|
|
# Note: $parts is already XML, does not need to be encoded further
|
|
|
|
|
$parts = $piece->parts;
|
|
|
|
|
$titleAccum = $parts[0]->out;
|
|
|
|
|
unset( $parts[0] );
|
|
|
|
|
|
|
|
|
|
$element = new PPNode_Hash_Tree( $name );
|
|
|
|
|
|
2008-04-14 07:45:50 +00:00
|
|
|
# The invocation is at the start of the line if lineStart is set in
|
2008-02-05 08:23:58 +00:00
|
|
|
# the stack, and all opening brackets are used up.
|
|
|
|
|
if ( $maxCount == $matchingCount && !empty( $piece->lineStart ) ) {
|
|
|
|
|
$element->addChild( new PPNode_Hash_Attr( 'lineStart', 1 ) );
|
|
|
|
|
}
|
|
|
|
|
$titleNode = new PPNode_Hash_Tree( 'title' );
|
|
|
|
|
$titleNode->firstChild = $titleAccum->firstNode;
|
|
|
|
|
$titleNode->lastChild = $titleAccum->lastNode;
|
|
|
|
|
$element->addChild( $titleNode );
|
|
|
|
|
$argIndex = 1;
|
2010-10-14 20:53:04 +00:00
|
|
|
foreach ( $parts as $part ) {
|
2008-02-05 08:23:58 +00:00
|
|
|
if ( isset( $part->eqpos ) ) {
|
|
|
|
|
// Find equals
|
|
|
|
|
$lastNode = false;
|
|
|
|
|
for ( $node = $part->out->firstNode; $node; $node = $node->nextSibling ) {
|
|
|
|
|
if ( $node === $part->eqpos ) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
$lastNode = $node;
|
|
|
|
|
}
|
|
|
|
|
if ( !$node ) {
|
2013-02-03 19:42:08 +00:00
|
|
|
throw new MWException( __METHOD__ . ': eqpos not found' );
|
2008-02-05 08:23:58 +00:00
|
|
|
}
|
|
|
|
|
if ( $node->name !== 'equals' ) {
|
2013-02-03 19:42:08 +00:00
|
|
|
throw new MWException( __METHOD__ . ': eqpos is not equals' );
|
2008-02-05 08:23:58 +00:00
|
|
|
}
|
|
|
|
|
$equalsNode = $node;
|
|
|
|
|
|
|
|
|
|
// Construct name node
|
|
|
|
|
$nameNode = new PPNode_Hash_Tree( 'name' );
|
|
|
|
|
if ( $lastNode !== false ) {
|
|
|
|
|
$lastNode->nextSibling = false;
|
|
|
|
|
$nameNode->firstChild = $part->out->firstNode;
|
|
|
|
|
$nameNode->lastChild = $lastNode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Construct value node
|
|
|
|
|
$valueNode = new PPNode_Hash_Tree( 'value' );
|
|
|
|
|
if ( $equalsNode->nextSibling !== false ) {
|
|
|
|
|
$valueNode->firstChild = $equalsNode->nextSibling;
|
|
|
|
|
$valueNode->lastChild = $part->out->lastNode;
|
|
|
|
|
}
|
|
|
|
|
$partNode = new PPNode_Hash_Tree( 'part' );
|
|
|
|
|
$partNode->addChild( $nameNode );
|
|
|
|
|
$partNode->addChild( $equalsNode->firstChild );
|
|
|
|
|
$partNode->addChild( $valueNode );
|
|
|
|
|
$element->addChild( $partNode );
|
|
|
|
|
} else {
|
|
|
|
|
$partNode = new PPNode_Hash_Tree( 'part' );
|
|
|
|
|
$nameNode = new PPNode_Hash_Tree( 'name' );
|
|
|
|
|
$nameNode->addChild( new PPNode_Hash_Attr( 'index', $argIndex++ ) );
|
|
|
|
|
$valueNode = new PPNode_Hash_Tree( 'value' );
|
|
|
|
|
$valueNode->firstChild = $part->out->firstNode;
|
|
|
|
|
$valueNode->lastChild = $part->out->lastNode;
|
|
|
|
|
$partNode->addChild( $nameNode );
|
|
|
|
|
$partNode->addChild( $valueNode );
|
|
|
|
|
$element->addChild( $partNode );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Advance input pointer
|
|
|
|
|
$i += $matchingCount;
|
|
|
|
|
|
|
|
|
|
# Unwind the stack
|
|
|
|
|
$stack->pop();
|
|
|
|
|
$accum =& $stack->getAccum();
|
|
|
|
|
|
|
|
|
|
# Re-add the old stack element if it still has unmatched opening characters remaining
|
2013-02-03 19:42:08 +00:00
|
|
|
if ( $matchingCount < $piece->count ) {
|
2008-02-05 08:23:58 +00:00
|
|
|
$piece->parts = array( new PPDPart_Hash );
|
|
|
|
|
$piece->count -= $matchingCount;
|
|
|
|
|
# do we still qualify for any callback with remaining count?
|
2012-05-22 23:56:33 +00:00
|
|
|
$min = $rules[$piece->open]['min'];
|
|
|
|
|
if ( $piece->count >= $min ) {
|
|
|
|
|
$stack->push( $piece );
|
|
|
|
|
$accum =& $stack->getAccum();
|
|
|
|
|
} else {
|
|
|
|
|
$accum->addLiteral( str_repeat( $piece->open, $piece->count ) );
|
2008-02-05 08:23:58 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extract( $stack->getFlags() );
|
|
|
|
|
|
|
|
|
|
# Add XML element to the enclosing accumulator
|
|
|
|
|
if ( $element instanceof PPNode ) {
|
|
|
|
|
$accum->addNode( $element );
|
|
|
|
|
} else {
|
|
|
|
|
$accum->addAccum( $element );
|
|
|
|
|
}
|
2011-05-29 14:01:47 +00:00
|
|
|
} elseif ( $found == 'pipe' ) {
|
2008-02-05 08:23:58 +00:00
|
|
|
$findEquals = true; // shortcut for getFlags()
|
|
|
|
|
$stack->addPart();
|
|
|
|
|
$accum =& $stack->getAccum();
|
|
|
|
|
++$i;
|
2011-05-29 14:01:47 +00:00
|
|
|
} elseif ( $found == 'equals' ) {
|
2008-02-05 08:23:58 +00:00
|
|
|
$findEquals = false; // shortcut for getFlags()
|
|
|
|
|
$accum->addNodeWithText( 'equals', '=' );
|
|
|
|
|
$stack->getCurrentPart()->eqpos = $accum->lastNode;
|
|
|
|
|
++$i;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Output any remaining unclosed brackets
|
|
|
|
|
foreach ( $stack->stack as $piece ) {
|
|
|
|
|
$stack->rootAccum->addAccum( $piece->breakSyntax() );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Enable top-level headings
|
|
|
|
|
for ( $node = $stack->rootAccum->firstNode; $node; $node = $node->nextSibling ) {
|
|
|
|
|
if ( isset( $node->name ) && $node->name === 'possible-h' ) {
|
|
|
|
|
$node->name = 'h';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$rootNode = new PPNode_Hash_Tree( 'root' );
|
|
|
|
|
$rootNode->firstChild = $stack->rootAccum->firstNode;
|
|
|
|
|
$rootNode->lastChild = $stack->rootAccum->lastNode;
|
2010-12-11 03:52:35 +00:00
|
|
|
|
2009-02-09 23:18:37 +00:00
|
|
|
// Cache
|
2013-02-03 19:42:08 +00:00
|
|
|
if ( $cacheable ) {
|
2010-09-11 21:55:21 +00:00
|
|
|
$cacheValue = sprintf( "%08d", self::CACHE_VERSION ) . serialize( $rootNode );
|
2009-02-09 23:18:37 +00:00
|
|
|
$wgMemc->set( $cacheKey, $cacheValue, 86400 );
|
2013-02-03 19:42:08 +00:00
|
|
|
wfProfileOut( __METHOD__ . '-cache-miss' );
|
|
|
|
|
wfProfileOut( __METHOD__ . '-cacheable' );
|
2009-02-09 23:18:37 +00:00
|
|
|
wfDebugLog( "Preprocessor", "Saved preprocessor Hash to memcached (key $cacheKey)" );
|
|
|
|
|
}
|
2010-12-11 03:52:35 +00:00
|
|
|
|
2009-02-09 23:18:37 +00:00
|
|
|
wfProfileOut( __METHOD__ );
|
2008-02-05 08:23:58 +00:00
|
|
|
return $rootNode;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Stack class to help Preprocessor::preprocessToObj()
|
WARNING: HUGE COMMIT
Doxygen documentation update:
* Changed alls @addtogroup to @ingroup. @addtogroup adds the comment to the group description, but doesn't add the file, class, function, ... to the group like @ingroup does. See for example http://svn.wikimedia.org/doc/group__SpecialPage.html where it's impossible to see related files, classes, ... that should belong to that group.
* Added @file to file description, it seems that it should be explicitely decalred for file descriptions, otherwise doxygen will think that the comment document the first class, variabled, function, ... that is in that file.
* Removed some empty comments
* Removed some ?>
Added following groups:
* ExternalStorage
* JobQueue
* MaintenanceLanguage
One more thing: there are still a lot of warnings when generating the doc.
2008-05-20 17:13:28 +00:00
|
|
|
* @ingroup Parser
|
2008-02-05 08:23:58 +00:00
|
|
|
*/
|
|
|
|
|
class PPDStack_Hash extends PPDStack {
|
|
|
|
|
function __construct() {
|
|
|
|
|
$this->elementClass = 'PPDStackElement_Hash';
|
|
|
|
|
parent::__construct();
|
|
|
|
|
$this->rootAccum = new PPDAccum_Hash;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
WARNING: HUGE COMMIT
Doxygen documentation update:
* Changed alls @addtogroup to @ingroup. @addtogroup adds the comment to the group description, but doesn't add the file, class, function, ... to the group like @ingroup does. See for example http://svn.wikimedia.org/doc/group__SpecialPage.html where it's impossible to see related files, classes, ... that should belong to that group.
* Added @file to file description, it seems that it should be explicitely decalred for file descriptions, otherwise doxygen will think that the comment document the first class, variabled, function, ... that is in that file.
* Removed some empty comments
* Removed some ?>
Added following groups:
* ExternalStorage
* JobQueue
* MaintenanceLanguage
One more thing: there are still a lot of warnings when generating the doc.
2008-05-20 17:13:28 +00:00
|
|
|
/**
|
|
|
|
|
* @ingroup Parser
|
|
|
|
|
*/
|
2008-02-05 08:23:58 +00:00
|
|
|
class PPDStackElement_Hash extends PPDStackElement {
|
|
|
|
|
function __construct( $data = array() ) {
|
|
|
|
|
$this->partClass = 'PPDPart_Hash';
|
|
|
|
|
parent::__construct( $data );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get the accumulator that would result if the close is not found.
|
2011-05-29 14:01:47 +00:00
|
|
|
*
|
|
|
|
|
* @return PPDAccum_Hash
|
2008-02-05 08:23:58 +00:00
|
|
|
*/
|
|
|
|
|
function breakSyntax( $openingCount = false ) {
|
|
|
|
|
if ( $this->open == "\n" ) {
|
|
|
|
|
$accum = $this->parts[0]->out;
|
|
|
|
|
} else {
|
|
|
|
|
if ( $openingCount === false ) {
|
|
|
|
|
$openingCount = $this->count;
|
|
|
|
|
}
|
|
|
|
|
$accum = new PPDAccum_Hash;
|
|
|
|
|
$accum->addLiteral( str_repeat( $this->open, $openingCount ) );
|
|
|
|
|
$first = true;
|
|
|
|
|
foreach ( $this->parts as $part ) {
|
|
|
|
|
if ( $first ) {
|
|
|
|
|
$first = false;
|
|
|
|
|
} else {
|
|
|
|
|
$accum->addLiteral( '|' );
|
|
|
|
|
}
|
|
|
|
|
$accum->addAccum( $part->out );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return $accum;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
WARNING: HUGE COMMIT
Doxygen documentation update:
* Changed alls @addtogroup to @ingroup. @addtogroup adds the comment to the group description, but doesn't add the file, class, function, ... to the group like @ingroup does. See for example http://svn.wikimedia.org/doc/group__SpecialPage.html where it's impossible to see related files, classes, ... that should belong to that group.
* Added @file to file description, it seems that it should be explicitely decalred for file descriptions, otherwise doxygen will think that the comment document the first class, variabled, function, ... that is in that file.
* Removed some empty comments
* Removed some ?>
Added following groups:
* ExternalStorage
* JobQueue
* MaintenanceLanguage
One more thing: there are still a lot of warnings when generating the doc.
2008-05-20 17:13:28 +00:00
|
|
|
/**
|
|
|
|
|
* @ingroup Parser
|
|
|
|
|
*/
|
2008-02-05 08:23:58 +00:00
|
|
|
class PPDPart_Hash extends PPDPart {
|
|
|
|
|
function __construct( $out = '' ) {
|
|
|
|
|
$accum = new PPDAccum_Hash;
|
|
|
|
|
if ( $out !== '' ) {
|
|
|
|
|
$accum->addLiteral( $out );
|
|
|
|
|
}
|
|
|
|
|
parent::__construct( $accum );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
WARNING: HUGE COMMIT
Doxygen documentation update:
* Changed alls @addtogroup to @ingroup. @addtogroup adds the comment to the group description, but doesn't add the file, class, function, ... to the group like @ingroup does. See for example http://svn.wikimedia.org/doc/group__SpecialPage.html where it's impossible to see related files, classes, ... that should belong to that group.
* Added @file to file description, it seems that it should be explicitely decalred for file descriptions, otherwise doxygen will think that the comment document the first class, variabled, function, ... that is in that file.
* Removed some empty comments
* Removed some ?>
Added following groups:
* ExternalStorage
* JobQueue
* MaintenanceLanguage
One more thing: there are still a lot of warnings when generating the doc.
2008-05-20 17:13:28 +00:00
|
|
|
/**
|
|
|
|
|
* @ingroup Parser
|
|
|
|
|
*/
|
2008-02-05 08:23:58 +00:00
|
|
|
class PPDAccum_Hash {
|
|
|
|
|
var $firstNode, $lastNode;
|
|
|
|
|
|
|
|
|
|
function __construct() {
|
|
|
|
|
$this->firstNode = $this->lastNode = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Append a string literal
|
|
|
|
|
*/
|
|
|
|
|
function addLiteral( $s ) {
|
|
|
|
|
if ( $this->lastNode === false ) {
|
|
|
|
|
$this->firstNode = $this->lastNode = new PPNode_Hash_Text( $s );
|
|
|
|
|
} elseif ( $this->lastNode instanceof PPNode_Hash_Text ) {
|
|
|
|
|
$this->lastNode->value .= $s;
|
|
|
|
|
} else {
|
|
|
|
|
$this->lastNode->nextSibling = new PPNode_Hash_Text( $s );
|
|
|
|
|
$this->lastNode = $this->lastNode->nextSibling;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Append a PPNode
|
|
|
|
|
*/
|
|
|
|
|
function addNode( PPNode $node ) {
|
|
|
|
|
if ( $this->lastNode === false ) {
|
|
|
|
|
$this->firstNode = $this->lastNode = $node;
|
|
|
|
|
} else {
|
|
|
|
|
$this->lastNode->nextSibling = $node;
|
|
|
|
|
$this->lastNode = $node;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Append a tree node with text contents
|
|
|
|
|
*/
|
|
|
|
|
function addNodeWithText( $name, $value ) {
|
|
|
|
|
$node = PPNode_Hash_Tree::newWithText( $name, $value );
|
|
|
|
|
$this->addNode( $node );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Append a PPAccum_Hash
|
2008-04-14 07:45:50 +00:00
|
|
|
* Takes over ownership of the nodes in the source argument. These nodes may
|
2008-02-05 08:23:58 +00:00
|
|
|
* subsequently be modified, especially nextSibling.
|
|
|
|
|
*/
|
|
|
|
|
function addAccum( $accum ) {
|
|
|
|
|
if ( $accum->lastNode === false ) {
|
|
|
|
|
// nothing to add
|
|
|
|
|
} elseif ( $this->lastNode === false ) {
|
|
|
|
|
$this->firstNode = $accum->firstNode;
|
|
|
|
|
$this->lastNode = $accum->lastNode;
|
|
|
|
|
} else {
|
|
|
|
|
$this->lastNode->nextSibling = $accum->firstNode;
|
|
|
|
|
$this->lastNode = $accum->lastNode;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* An expansion frame, used as a context to expand the result of preprocessToObj()
|
WARNING: HUGE COMMIT
Doxygen documentation update:
* Changed alls @addtogroup to @ingroup. @addtogroup adds the comment to the group description, but doesn't add the file, class, function, ... to the group like @ingroup does. See for example http://svn.wikimedia.org/doc/group__SpecialPage.html where it's impossible to see related files, classes, ... that should belong to that group.
* Added @file to file description, it seems that it should be explicitely decalred for file descriptions, otherwise doxygen will think that the comment document the first class, variabled, function, ... that is in that file.
* Removed some empty comments
* Removed some ?>
Added following groups:
* ExternalStorage
* JobQueue
* MaintenanceLanguage
One more thing: there are still a lot of warnings when generating the doc.
2008-05-20 17:13:28 +00:00
|
|
|
* @ingroup Parser
|
2008-02-05 08:23:58 +00:00
|
|
|
*/
|
|
|
|
|
class PPFrame_Hash implements PPFrame {
|
2011-02-24 11:59:51 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @var Parser
|
|
|
|
|
*/
|
|
|
|
|
var $parser;
|
|
|
|
|
|
2011-02-24 17:04:49 +00:00
|
|
|
/**
|
|
|
|
|
* @var Preprocessor
|
|
|
|
|
*/
|
|
|
|
|
var $preprocessor;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @var Title
|
|
|
|
|
*/
|
|
|
|
|
var $title;
|
2008-02-05 08:23:58 +00:00
|
|
|
var $titleCache;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Hashtable listing templates which are disallowed for expansion in this frame,
|
|
|
|
|
* having been encountered previously in parent frames.
|
|
|
|
|
*/
|
|
|
|
|
var $loopCheckHash;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Recursion depth of this frame, top = 0
|
2008-10-23 14:40:10 +00:00
|
|
|
* Note that this is NOT the same as expansion depth in expand()
|
2008-02-05 08:23:58 +00:00
|
|
|
*/
|
|
|
|
|
var $depth;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Construct a new preprocessor frame.
|
2010-06-09 14:57:59 +00:00
|
|
|
* @param $preprocessor Preprocessor: the parent preprocessor
|
2008-02-05 08:23:58 +00:00
|
|
|
*/
|
|
|
|
|
function __construct( $preprocessor ) {
|
|
|
|
|
$this->preprocessor = $preprocessor;
|
|
|
|
|
$this->parser = $preprocessor->parser;
|
|
|
|
|
$this->title = $this->parser->mTitle;
|
|
|
|
|
$this->titleCache = array( $this->title ? $this->title->getPrefixedDBkey() : false );
|
|
|
|
|
$this->loopCheckHash = array();
|
|
|
|
|
$this->depth = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create a new child frame
|
|
|
|
|
* $args is optionally a multi-root PPNode or array containing the template arguments
|
2011-05-29 14:01:47 +00:00
|
|
|
*
|
2012-10-07 23:35:26 +00:00
|
|
|
* @param array|bool|\PPNode_Hash_Array $args PPNode_Hash_Array|array
|
2012-02-09 18:01:10 +00:00
|
|
|
* @param $title Title|bool
|
2011-05-29 14:01:47 +00:00
|
|
|
*
|
2012-10-07 23:35:26 +00:00
|
|
|
* @param int $indexOffset
|
|
|
|
|
* @throws MWException
|
2011-05-29 14:01:47 +00:00
|
|
|
* @return PPTemplateFrame_Hash
|
2008-02-05 08:23:58 +00:00
|
|
|
*/
|
2012-05-22 03:26:25 +00:00
|
|
|
function newChild( $args = false, $title = false, $indexOffset = 0 ) {
|
2008-02-05 08:23:58 +00:00
|
|
|
$namedArgs = array();
|
|
|
|
|
$numberedArgs = array();
|
|
|
|
|
if ( $title === false ) {
|
|
|
|
|
$title = $this->title;
|
|
|
|
|
}
|
|
|
|
|
if ( $args !== false ) {
|
|
|
|
|
if ( $args instanceof PPNode_Hash_Array ) {
|
|
|
|
|
$args = $args->value;
|
|
|
|
|
} elseif ( !is_array( $args ) ) {
|
|
|
|
|
throw new MWException( __METHOD__ . ': $args must be array or PPNode_Hash_Array' );
|
|
|
|
|
}
|
|
|
|
|
foreach ( $args as $arg ) {
|
|
|
|
|
$bits = $arg->splitArg();
|
|
|
|
|
if ( $bits['index'] !== '' ) {
|
|
|
|
|
// Numbered parameter
|
2012-05-22 03:26:25 +00:00
|
|
|
$index = $bits['index'] - $indexOffset;
|
|
|
|
|
$numberedArgs[$index] = $bits['value'];
|
|
|
|
|
unset( $namedArgs[$index] );
|
2008-02-05 08:23:58 +00:00
|
|
|
} else {
|
|
|
|
|
// Named parameter
|
|
|
|
|
$name = trim( $this->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
|
|
|
|
|
$namedArgs[$name] = $bits['value'];
|
|
|
|
|
unset( $numberedArgs[$name] );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return new PPTemplateFrame_Hash( $this->preprocessor, $this, $numberedArgs, $namedArgs, $title );
|
|
|
|
|
}
|
|
|
|
|
|
2011-05-29 14:01:47 +00:00
|
|
|
/**
|
|
|
|
|
* @throws MWException
|
|
|
|
|
* @param $root
|
|
|
|
|
* @param $flags int
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
2008-02-05 08:23:58 +00:00
|
|
|
function expand( $root, $flags = 0 ) {
|
2008-10-23 14:40:10 +00:00
|
|
|
static $expansionDepth = 0;
|
2008-02-05 08:23:58 +00:00
|
|
|
if ( is_string( $root ) ) {
|
|
|
|
|
return $root;
|
|
|
|
|
}
|
|
|
|
|
|
2011-02-24 17:04:49 +00:00
|
|
|
if ( ++$this->parser->mPPNodeCount > $this->parser->mOptions->getMaxPPNodeCount() ) {
|
2012-04-21 11:09:03 +00:00
|
|
|
$this->parser->limitationWarn( 'node-count-exceeded',
|
|
|
|
|
$this->parser->mPPNodeCount,
|
|
|
|
|
$this->parser->mOptions->getMaxPPNodeCount()
|
|
|
|
|
);
|
2008-02-05 08:23:58 +00:00
|
|
|
return '<span class="error">Node-count limit exceeded</span>';
|
|
|
|
|
}
|
2010-08-05 19:01:47 +00:00
|
|
|
if ( $expansionDepth > $this->parser->mOptions->getMaxPPExpandDepth() ) {
|
2012-04-21 11:09:03 +00:00
|
|
|
$this->parser->limitationWarn( 'expansion-depth-exceeded',
|
|
|
|
|
$expansionDepth,
|
|
|
|
|
$this->parser->mOptions->getMaxPPExpandDepth()
|
|
|
|
|
);
|
2008-03-25 04:26:58 +00:00
|
|
|
return '<span class="error">Expansion depth limit exceeded</span>';
|
|
|
|
|
}
|
2008-10-23 14:40:10 +00:00
|
|
|
++$expansionDepth;
|
2012-05-04 20:44:14 +00:00
|
|
|
if ( $expansionDepth > $this->parser->mHighestExpansionDepth ) {
|
|
|
|
|
$this->parser->mHighestExpansionDepth = $expansionDepth;
|
|
|
|
|
}
|
2008-02-05 08:23:58 +00:00
|
|
|
|
|
|
|
|
$outStack = array( '', '' );
|
|
|
|
|
$iteratorStack = array( false, $root );
|
|
|
|
|
$indexStack = array( 0, 0 );
|
|
|
|
|
|
|
|
|
|
while ( count( $iteratorStack ) > 1 ) {
|
|
|
|
|
$level = count( $outStack ) - 1;
|
|
|
|
|
$iteratorNode =& $iteratorStack[ $level ];
|
|
|
|
|
$out =& $outStack[$level];
|
|
|
|
|
$index =& $indexStack[$level];
|
|
|
|
|
|
|
|
|
|
if ( is_array( $iteratorNode ) ) {
|
|
|
|
|
if ( $index >= count( $iteratorNode ) ) {
|
|
|
|
|
// All done with this iterator
|
|
|
|
|
$iteratorStack[$level] = false;
|
|
|
|
|
$contextNode = false;
|
|
|
|
|
} else {
|
|
|
|
|
$contextNode = $iteratorNode[$index];
|
|
|
|
|
$index++;
|
|
|
|
|
}
|
|
|
|
|
} elseif ( $iteratorNode instanceof PPNode_Hash_Array ) {
|
|
|
|
|
if ( $index >= $iteratorNode->getLength() ) {
|
|
|
|
|
// All done with this iterator
|
|
|
|
|
$iteratorStack[$level] = false;
|
|
|
|
|
$contextNode = false;
|
|
|
|
|
} else {
|
|
|
|
|
$contextNode = $iteratorNode->item( $index );
|
|
|
|
|
$index++;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2008-04-14 07:45:50 +00:00
|
|
|
// Copy to $contextNode and then delete from iterator stack,
|
2008-02-05 08:23:58 +00:00
|
|
|
// because this is not an iterator but we do have to execute it once
|
|
|
|
|
$contextNode = $iteratorStack[$level];
|
|
|
|
|
$iteratorStack[$level] = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$newIterator = false;
|
|
|
|
|
|
|
|
|
|
if ( $contextNode === false ) {
|
|
|
|
|
// nothing to do
|
|
|
|
|
} elseif ( is_string( $contextNode ) ) {
|
|
|
|
|
$out .= $contextNode;
|
|
|
|
|
} elseif ( is_array( $contextNode ) || $contextNode instanceof PPNode_Hash_Array ) {
|
|
|
|
|
$newIterator = $contextNode;
|
|
|
|
|
} elseif ( $contextNode instanceof PPNode_Hash_Attr ) {
|
|
|
|
|
// No output
|
|
|
|
|
} elseif ( $contextNode instanceof PPNode_Hash_Text ) {
|
|
|
|
|
$out .= $contextNode->value;
|
|
|
|
|
} elseif ( $contextNode instanceof PPNode_Hash_Tree ) {
|
|
|
|
|
if ( $contextNode->name == 'template' ) {
|
|
|
|
|
# Double-brace expansion
|
|
|
|
|
$bits = $contextNode->splitTemplate();
|
2010-07-29 07:20:02 +00:00
|
|
|
if ( $flags & PPFrame::NO_TEMPLATES ) {
|
2008-02-05 08:23:58 +00:00
|
|
|
$newIterator = $this->virtualBracketedImplode( '{{', '|', '}}', $bits['title'], $bits['parts'] );
|
|
|
|
|
} else {
|
|
|
|
|
$ret = $this->parser->braceSubstitution( $bits, $this );
|
|
|
|
|
if ( isset( $ret['object'] ) ) {
|
|
|
|
|
$newIterator = $ret['object'];
|
|
|
|
|
} else {
|
|
|
|
|
$out .= $ret['text'];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} elseif ( $contextNode->name == 'tplarg' ) {
|
|
|
|
|
# Triple-brace expansion
|
|
|
|
|
$bits = $contextNode->splitTemplate();
|
2010-07-29 07:20:02 +00:00
|
|
|
if ( $flags & PPFrame::NO_ARGS ) {
|
2008-02-05 08:23:58 +00:00
|
|
|
$newIterator = $this->virtualBracketedImplode( '{{{', '|', '}}}', $bits['title'], $bits['parts'] );
|
|
|
|
|
} else {
|
|
|
|
|
$ret = $this->parser->argSubstitution( $bits, $this );
|
|
|
|
|
if ( isset( $ret['object'] ) ) {
|
|
|
|
|
$newIterator = $ret['object'];
|
|
|
|
|
} else {
|
|
|
|
|
$out .= $ret['text'];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} elseif ( $contextNode->name == 'comment' ) {
|
|
|
|
|
# HTML-style comment
|
|
|
|
|
# Remove it in HTML, pre+remove and STRIP_COMMENTS modes
|
2008-04-14 07:45:50 +00:00
|
|
|
if ( $this->parser->ot['html']
|
|
|
|
|
|| ( $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() )
|
2010-07-29 07:20:02 +00:00
|
|
|
|| ( $flags & PPFrame::STRIP_COMMENTS ) )
|
2008-02-05 08:23:58 +00:00
|
|
|
{
|
|
|
|
|
$out .= '';
|
|
|
|
|
}
|
|
|
|
|
# Add a strip marker in PST mode so that pstPass2() can run some old-fashioned regexes on the result
|
|
|
|
|
# Not in RECOVER_COMMENTS mode (extractSections) though
|
2013-02-03 19:42:08 +00:00
|
|
|
elseif ( $this->parser->ot['wiki'] && !( $flags & PPFrame::RECOVER_COMMENTS ) ) {
|
2008-02-05 08:23:58 +00:00
|
|
|
$out .= $this->parser->insertStripItem( $contextNode->firstChild->value );
|
|
|
|
|
}
|
|
|
|
|
# Recover the literal comment in RECOVER_COMMENTS and pre+no-remove
|
|
|
|
|
else {
|
|
|
|
|
$out .= $contextNode->firstChild->value;
|
|
|
|
|
}
|
|
|
|
|
} elseif ( $contextNode->name == 'ignore' ) {
|
|
|
|
|
# Output suppression used by <includeonly> etc.
|
|
|
|
|
# OT_WIKI will only respect <ignore> in substed templates.
|
2008-04-14 07:45:50 +00:00
|
|
|
# The other output types respect it unless NO_IGNORE is set.
|
2008-02-05 08:23:58 +00:00
|
|
|
# extractSections() sets NO_IGNORE and so never respects it.
|
2011-06-07 17:34:20 +00:00
|
|
|
if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] ) || ( $flags & PPFrame::NO_IGNORE ) ) {
|
2008-02-05 08:23:58 +00:00
|
|
|
$out .= $contextNode->firstChild->value;
|
|
|
|
|
} else {
|
|
|
|
|
//$out .= '';
|
|
|
|
|
}
|
|
|
|
|
} elseif ( $contextNode->name == 'ext' ) {
|
|
|
|
|
# Extension tag
|
|
|
|
|
$bits = $contextNode->splitExt() + array( 'attr' => null, 'inner' => null, 'close' => null );
|
|
|
|
|
$out .= $this->parser->extensionSubstitution( $bits, $this );
|
|
|
|
|
} elseif ( $contextNode->name == 'h' ) {
|
|
|
|
|
# Heading
|
|
|
|
|
if ( $this->parser->ot['html'] ) {
|
|
|
|
|
# Expand immediately and insert heading index marker
|
|
|
|
|
$s = '';
|
|
|
|
|
for ( $node = $contextNode->firstChild; $node; $node = $node->nextSibling ) {
|
|
|
|
|
$s .= $this->expand( $node, $flags );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$bits = $contextNode->splitHeading();
|
|
|
|
|
$titleText = $this->title->getPrefixedDBkey();
|
|
|
|
|
$this->parser->mHeadings[] = array( $titleText, $bits['i'] );
|
|
|
|
|
$serial = count( $this->parser->mHeadings ) - 1;
|
2008-03-27 00:00:25 +00:00
|
|
|
$marker = "{$this->parser->mUniqPrefix}-h-$serial-" . Parser::MARKER_SUFFIX;
|
2008-02-05 08:23:58 +00:00
|
|
|
$s = substr( $s, 0, $bits['level'] ) . $marker . substr( $s, $bits['level'] );
|
2011-02-28 02:40:39 +00:00
|
|
|
$this->parser->mStripState->addGeneral( $marker, '' );
|
2008-02-05 08:23:58 +00:00
|
|
|
$out .= $s;
|
|
|
|
|
} else {
|
|
|
|
|
# Expand in virtual stack
|
|
|
|
|
$newIterator = $contextNode->getChildren();
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
# Generic recursive expansion
|
|
|
|
|
$newIterator = $contextNode->getChildren();
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2013-02-03 19:42:08 +00:00
|
|
|
throw new MWException( __METHOD__ . ': Invalid parameter type' );
|
2008-02-05 08:23:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( $newIterator !== false ) {
|
|
|
|
|
$outStack[] = '';
|
|
|
|
|
$iteratorStack[] = $newIterator;
|
|
|
|
|
$indexStack[] = 0;
|
|
|
|
|
} elseif ( $iteratorStack[$level] === false ) {
|
|
|
|
|
// Return accumulated value to parent
|
|
|
|
|
// With tail recursion
|
|
|
|
|
while ( $iteratorStack[$level] === false && $level > 0 ) {
|
|
|
|
|
$outStack[$level - 1] .= $out;
|
|
|
|
|
array_pop( $outStack );
|
|
|
|
|
array_pop( $iteratorStack );
|
|
|
|
|
array_pop( $indexStack );
|
|
|
|
|
$level--;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2008-10-23 14:40:10 +00:00
|
|
|
--$expansionDepth;
|
2008-02-05 08:23:58 +00:00
|
|
|
return $outStack[0];
|
|
|
|
|
}
|
|
|
|
|
|
2011-05-29 14:01:47 +00:00
|
|
|
/**
|
|
|
|
|
* @param $sep
|
|
|
|
|
* @param $flags
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
2008-02-05 08:23:58 +00:00
|
|
|
function implodeWithFlags( $sep, $flags /*, ... */ ) {
|
|
|
|
|
$args = array_slice( func_get_args(), 2 );
|
|
|
|
|
|
|
|
|
|
$first = true;
|
|
|
|
|
$s = '';
|
|
|
|
|
foreach ( $args as $root ) {
|
|
|
|
|
if ( $root instanceof PPNode_Hash_Array ) {
|
|
|
|
|
$root = $root->value;
|
|
|
|
|
}
|
|
|
|
|
if ( !is_array( $root ) ) {
|
|
|
|
|
$root = array( $root );
|
|
|
|
|
}
|
|
|
|
|
foreach ( $root as $node ) {
|
|
|
|
|
if ( $first ) {
|
|
|
|
|
$first = false;
|
|
|
|
|
} else {
|
|
|
|
|
$s .= $sep;
|
|
|
|
|
}
|
|
|
|
|
$s .= $this->expand( $node, $flags );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return $s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Implode with no flags specified
|
|
|
|
|
* This previously called implodeWithFlags but has now been inlined to reduce stack depth
|
2011-05-29 14:01:47 +00:00
|
|
|
* @return string
|
2008-02-05 08:23:58 +00:00
|
|
|
*/
|
|
|
|
|
function implode( $sep /*, ... */ ) {
|
|
|
|
|
$args = array_slice( func_get_args(), 1 );
|
|
|
|
|
|
|
|
|
|
$first = true;
|
|
|
|
|
$s = '';
|
|
|
|
|
foreach ( $args as $root ) {
|
|
|
|
|
if ( $root instanceof PPNode_Hash_Array ) {
|
|
|
|
|
$root = $root->value;
|
|
|
|
|
}
|
|
|
|
|
if ( !is_array( $root ) ) {
|
|
|
|
|
$root = array( $root );
|
|
|
|
|
}
|
|
|
|
|
foreach ( $root as $node ) {
|
|
|
|
|
if ( $first ) {
|
|
|
|
|
$first = false;
|
|
|
|
|
} else {
|
|
|
|
|
$s .= $sep;
|
|
|
|
|
}
|
|
|
|
|
$s .= $this->expand( $node );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return $s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2008-04-14 07:45:50 +00:00
|
|
|
* Makes an object that, when expand()ed, will be the same as one obtained
|
2008-02-05 08:23:58 +00:00
|
|
|
* with implode()
|
2011-05-29 14:01:47 +00:00
|
|
|
*
|
|
|
|
|
* @return PPNode_Hash_Array
|
2008-02-05 08:23:58 +00:00
|
|
|
*/
|
|
|
|
|
function virtualImplode( $sep /*, ... */ ) {
|
|
|
|
|
$args = array_slice( func_get_args(), 1 );
|
|
|
|
|
$out = array();
|
|
|
|
|
$first = true;
|
|
|
|
|
|
|
|
|
|
foreach ( $args as $root ) {
|
|
|
|
|
if ( $root instanceof PPNode_Hash_Array ) {
|
|
|
|
|
$root = $root->value;
|
|
|
|
|
}
|
|
|
|
|
if ( !is_array( $root ) ) {
|
|
|
|
|
$root = array( $root );
|
|
|
|
|
}
|
|
|
|
|
foreach ( $root as $node ) {
|
|
|
|
|
if ( $first ) {
|
|
|
|
|
$first = false;
|
|
|
|
|
} else {
|
|
|
|
|
$out[] = $sep;
|
|
|
|
|
}
|
|
|
|
|
$out[] = $node;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return new PPNode_Hash_Array( $out );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Virtual implode with brackets
|
2011-05-29 14:01:47 +00:00
|
|
|
*
|
|
|
|
|
* @return PPNode_Hash_Array
|
2008-02-05 08:23:58 +00:00
|
|
|
*/
|
|
|
|
|
function virtualBracketedImplode( $start, $sep, $end /*, ... */ ) {
|
|
|
|
|
$args = array_slice( func_get_args(), 3 );
|
|
|
|
|
$out = array( $start );
|
|
|
|
|
$first = true;
|
|
|
|
|
|
|
|
|
|
foreach ( $args as $root ) {
|
|
|
|
|
if ( $root instanceof PPNode_Hash_Array ) {
|
|
|
|
|
$root = $root->value;
|
|
|
|
|
}
|
|
|
|
|
if ( !is_array( $root ) ) {
|
|
|
|
|
$root = array( $root );
|
|
|
|
|
}
|
|
|
|
|
foreach ( $root as $node ) {
|
|
|
|
|
if ( $first ) {
|
|
|
|
|
$first = false;
|
|
|
|
|
} else {
|
|
|
|
|
$out[] = $sep;
|
|
|
|
|
}
|
|
|
|
|
$out[] = $node;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
$out[] = $end;
|
|
|
|
|
return new PPNode_Hash_Array( $out );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function __toString() {
|
|
|
|
|
return 'frame{}';
|
|
|
|
|
}
|
|
|
|
|
|
2011-05-29 14:01:47 +00:00
|
|
|
/**
|
|
|
|
|
* @param $level bool
|
|
|
|
|
* @return array|bool|String
|
|
|
|
|
*/
|
2008-02-05 08:23:58 +00:00
|
|
|
function getPDBK( $level = false ) {
|
|
|
|
|
if ( $level === false ) {
|
|
|
|
|
return $this->title->getPrefixedDBkey();
|
|
|
|
|
} else {
|
|
|
|
|
return isset( $this->titleCache[$level] ) ? $this->titleCache[$level] : false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-05-29 14:01:47 +00:00
|
|
|
/**
|
|
|
|
|
* @return array
|
|
|
|
|
*/
|
2009-07-02 16:21:30 +00:00
|
|
|
function getArguments() {
|
|
|
|
|
return array();
|
|
|
|
|
}
|
|
|
|
|
|
2011-05-29 14:01:47 +00:00
|
|
|
/**
|
|
|
|
|
* @return array
|
|
|
|
|
*/
|
2009-07-02 16:21:30 +00:00
|
|
|
function getNumberedArguments() {
|
|
|
|
|
return array();
|
|
|
|
|
}
|
|
|
|
|
|
2011-05-29 14:01:47 +00:00
|
|
|
/**
|
|
|
|
|
* @return array
|
|
|
|
|
*/
|
2009-07-02 16:21:30 +00:00
|
|
|
function getNamedArguments() {
|
|
|
|
|
return array();
|
|
|
|
|
}
|
|
|
|
|
|
2008-02-05 08:23:58 +00:00
|
|
|
/**
|
|
|
|
|
* Returns true if there are no arguments in this frame
|
2011-05-29 14:01:47 +00:00
|
|
|
*
|
|
|
|
|
* @return bool
|
2008-02-05 08:23:58 +00:00
|
|
|
*/
|
|
|
|
|
function isEmpty() {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2011-05-29 14:01:47 +00:00
|
|
|
/**
|
|
|
|
|
* @param $name
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
2008-02-05 08:23:58 +00:00
|
|
|
function getArgument( $name ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns true if the infinite loop check is OK, false if a loop is detected
|
2011-02-24 17:04:49 +00:00
|
|
|
*
|
|
|
|
|
* @param $title Title
|
2011-05-29 14:01:47 +00:00
|
|
|
*
|
|
|
|
|
* @return bool
|
2008-02-05 08:23:58 +00:00
|
|
|
*/
|
|
|
|
|
function loopCheck( $title ) {
|
|
|
|
|
return !isset( $this->loopCheckHash[$title->getPrefixedDBkey()] );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Return true if the frame is a template frame
|
2011-05-29 14:01:47 +00:00
|
|
|
*
|
|
|
|
|
* @return bool
|
2008-02-05 08:23:58 +00:00
|
|
|
*/
|
|
|
|
|
function isTemplate() {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2011-11-09 20:52:24 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get a title of frame
|
|
|
|
|
*
|
|
|
|
|
* @return Title
|
|
|
|
|
*/
|
|
|
|
|
function getTitle() {
|
|
|
|
|
return $this->title;
|
|
|
|
|
}
|
2008-02-05 08:23:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Expansion frame with template arguments
|
WARNING: HUGE COMMIT
Doxygen documentation update:
* Changed alls @addtogroup to @ingroup. @addtogroup adds the comment to the group description, but doesn't add the file, class, function, ... to the group like @ingroup does. See for example http://svn.wikimedia.org/doc/group__SpecialPage.html where it's impossible to see related files, classes, ... that should belong to that group.
* Added @file to file description, it seems that it should be explicitely decalred for file descriptions, otherwise doxygen will think that the comment document the first class, variabled, function, ... that is in that file.
* Removed some empty comments
* Removed some ?>
Added following groups:
* ExternalStorage
* JobQueue
* MaintenanceLanguage
One more thing: there are still a lot of warnings when generating the doc.
2008-05-20 17:13:28 +00:00
|
|
|
* @ingroup Parser
|
2008-02-05 08:23:58 +00:00
|
|
|
*/
|
|
|
|
|
class PPTemplateFrame_Hash extends PPFrame_Hash {
|
|
|
|
|
var $numberedArgs, $namedArgs, $parent;
|
|
|
|
|
var $numberedExpansionCache, $namedExpansionCache;
|
|
|
|
|
|
2011-05-26 19:52:56 +00:00
|
|
|
/**
|
|
|
|
|
* @param $preprocessor
|
|
|
|
|
* @param $parent
|
|
|
|
|
* @param $numberedArgs array
|
|
|
|
|
* @param $namedArgs array
|
|
|
|
|
* @param $title Title
|
|
|
|
|
*/
|
2008-02-05 08:23:58 +00:00
|
|
|
function __construct( $preprocessor, $parent = false, $numberedArgs = array(), $namedArgs = array(), $title = false ) {
|
2010-07-25 21:15:27 +00:00
|
|
|
parent::__construct( $preprocessor );
|
|
|
|
|
|
2008-02-05 08:23:58 +00:00
|
|
|
$this->parent = $parent;
|
|
|
|
|
$this->numberedArgs = $numberedArgs;
|
|
|
|
|
$this->namedArgs = $namedArgs;
|
|
|
|
|
$this->title = $title;
|
|
|
|
|
$pdbk = $title ? $title->getPrefixedDBkey() : false;
|
|
|
|
|
$this->titleCache = $parent->titleCache;
|
|
|
|
|
$this->titleCache[] = $pdbk;
|
|
|
|
|
$this->loopCheckHash = /*clone*/ $parent->loopCheckHash;
|
|
|
|
|
if ( $pdbk !== false ) {
|
|
|
|
|
$this->loopCheckHash[$pdbk] = true;
|
|
|
|
|
}
|
|
|
|
|
$this->depth = $parent->depth + 1;
|
|
|
|
|
$this->numberedExpansionCache = $this->namedExpansionCache = array();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function __toString() {
|
|
|
|
|
$s = 'tplframe{';
|
|
|
|
|
$first = true;
|
|
|
|
|
$args = $this->numberedArgs + $this->namedArgs;
|
|
|
|
|
foreach ( $args as $name => $value ) {
|
|
|
|
|
if ( $first ) {
|
|
|
|
|
$first = false;
|
|
|
|
|
} else {
|
|
|
|
|
$s .= ', ';
|
|
|
|
|
}
|
2008-04-14 07:45:50 +00:00
|
|
|
$s .= "\"$name\":\"" .
|
2008-02-05 08:23:58 +00:00
|
|
|
str_replace( '"', '\\"', $value->__toString() ) . '"';
|
|
|
|
|
}
|
|
|
|
|
$s .= '}';
|
|
|
|
|
return $s;
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* Returns true if there are no arguments in this frame
|
2011-05-26 19:52:56 +00:00
|
|
|
*
|
|
|
|
|
* @return bool
|
2008-02-05 08:23:58 +00:00
|
|
|
*/
|
|
|
|
|
function isEmpty() {
|
|
|
|
|
return !count( $this->numberedArgs ) && !count( $this->namedArgs );
|
|
|
|
|
}
|
|
|
|
|
|
2011-05-26 19:52:56 +00:00
|
|
|
/**
|
|
|
|
|
* @return array
|
|
|
|
|
*/
|
2008-08-09 06:08:54 +00:00
|
|
|
function getArguments() {
|
|
|
|
|
$arguments = array();
|
|
|
|
|
foreach ( array_merge(
|
2013-02-03 19:42:08 +00:00
|
|
|
array_keys( $this->numberedArgs ),
|
|
|
|
|
array_keys( $this->namedArgs ) ) as $key ) {
|
|
|
|
|
$arguments[$key] = $this->getArgument( $key );
|
2008-08-09 06:08:54 +00:00
|
|
|
}
|
|
|
|
|
return $arguments;
|
|
|
|
|
}
|
2010-12-11 03:52:35 +00:00
|
|
|
|
2011-05-26 19:52:56 +00:00
|
|
|
/**
|
|
|
|
|
* @return array
|
|
|
|
|
*/
|
2008-08-09 06:08:54 +00:00
|
|
|
function getNumberedArguments() {
|
|
|
|
|
$arguments = array();
|
2013-02-03 19:42:08 +00:00
|
|
|
foreach ( array_keys( $this->numberedArgs ) as $key ) {
|
|
|
|
|
$arguments[$key] = $this->getArgument( $key );
|
2008-08-09 06:08:54 +00:00
|
|
|
}
|
|
|
|
|
return $arguments;
|
|
|
|
|
}
|
2010-12-11 03:52:35 +00:00
|
|
|
|
2011-05-26 19:52:56 +00:00
|
|
|
/**
|
|
|
|
|
* @return array
|
|
|
|
|
*/
|
2008-08-09 06:08:54 +00:00
|
|
|
function getNamedArguments() {
|
|
|
|
|
$arguments = array();
|
2013-02-03 19:42:08 +00:00
|
|
|
foreach ( array_keys( $this->namedArgs ) as $key ) {
|
|
|
|
|
$arguments[$key] = $this->getArgument( $key );
|
2008-08-09 06:08:54 +00:00
|
|
|
}
|
|
|
|
|
return $arguments;
|
|
|
|
|
}
|
|
|
|
|
|
2011-05-29 14:01:47 +00:00
|
|
|
/**
|
|
|
|
|
* @param $index
|
|
|
|
|
* @return array|bool
|
|
|
|
|
*/
|
2008-02-05 08:23:58 +00:00
|
|
|
function getNumberedArgument( $index ) {
|
|
|
|
|
if ( !isset( $this->numberedArgs[$index] ) ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if ( !isset( $this->numberedExpansionCache[$index] ) ) {
|
|
|
|
|
# No trimming for unnamed arguments
|
2010-07-29 07:20:02 +00:00
|
|
|
$this->numberedExpansionCache[$index] = $this->parent->expand( $this->numberedArgs[$index], PPFrame::STRIP_COMMENTS );
|
2008-02-05 08:23:58 +00:00
|
|
|
}
|
|
|
|
|
return $this->numberedExpansionCache[$index];
|
|
|
|
|
}
|
|
|
|
|
|
2011-05-29 14:01:47 +00:00
|
|
|
/**
|
|
|
|
|
* @param $name
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
2008-02-05 08:23:58 +00:00
|
|
|
function getNamedArgument( $name ) {
|
|
|
|
|
if ( !isset( $this->namedArgs[$name] ) ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if ( !isset( $this->namedExpansionCache[$name] ) ) {
|
|
|
|
|
# Trim named arguments post-expand, for backwards compatibility
|
2008-04-14 07:45:50 +00:00
|
|
|
$this->namedExpansionCache[$name] = trim(
|
2010-07-29 07:20:02 +00:00
|
|
|
$this->parent->expand( $this->namedArgs[$name], PPFrame::STRIP_COMMENTS ) );
|
2008-02-05 08:23:58 +00:00
|
|
|
}
|
|
|
|
|
return $this->namedExpansionCache[$name];
|
|
|
|
|
}
|
|
|
|
|
|
2011-05-29 14:01:47 +00:00
|
|
|
/**
|
|
|
|
|
* @param $name
|
|
|
|
|
* @return array|bool
|
|
|
|
|
*/
|
2008-02-05 08:23:58 +00:00
|
|
|
function getArgument( $name ) {
|
|
|
|
|
$text = $this->getNumberedArgument( $name );
|
|
|
|
|
if ( $text === false ) {
|
|
|
|
|
$text = $this->getNamedArgument( $name );
|
|
|
|
|
}
|
|
|
|
|
return $text;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Return true if the frame is a template frame
|
2011-05-29 14:01:47 +00:00
|
|
|
*
|
|
|
|
|
* @return bool
|
2008-02-05 08:23:58 +00:00
|
|
|
*/
|
|
|
|
|
function isTemplate() {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2008-06-26 13:05:40 +00:00
|
|
|
/**
|
|
|
|
|
* Expansion frame with custom arguments
|
|
|
|
|
* @ingroup Parser
|
|
|
|
|
*/
|
|
|
|
|
class PPCustomFrame_Hash extends PPFrame_Hash {
|
|
|
|
|
var $args;
|
|
|
|
|
|
|
|
|
|
function __construct( $preprocessor, $args ) {
|
2010-07-25 21:15:27 +00:00
|
|
|
parent::__construct( $preprocessor );
|
2008-06-26 13:05:40 +00:00
|
|
|
$this->args = $args;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function __toString() {
|
|
|
|
|
$s = 'cstmframe{';
|
|
|
|
|
$first = true;
|
|
|
|
|
foreach ( $this->args as $name => $value ) {
|
|
|
|
|
if ( $first ) {
|
|
|
|
|
$first = false;
|
|
|
|
|
} else {
|
|
|
|
|
$s .= ', ';
|
|
|
|
|
}
|
|
|
|
|
$s .= "\"$name\":\"" .
|
|
|
|
|
str_replace( '"', '\\"', $value->__toString() ) . '"';
|
|
|
|
|
}
|
|
|
|
|
$s .= '}';
|
|
|
|
|
return $s;
|
|
|
|
|
}
|
|
|
|
|
|
2011-05-29 14:01:47 +00:00
|
|
|
/**
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
2008-06-26 13:05:40 +00:00
|
|
|
function isEmpty() {
|
|
|
|
|
return !count( $this->args );
|
|
|
|
|
}
|
|
|
|
|
|
2011-05-29 14:01:47 +00:00
|
|
|
/**
|
|
|
|
|
* @param $index
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
2008-06-26 13:05:40 +00:00
|
|
|
function getArgument( $index ) {
|
2008-11-03 00:04:33 +00:00
|
|
|
if ( !isset( $this->args[$index] ) ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2008-06-26 13:05:40 +00:00
|
|
|
return $this->args[$index];
|
|
|
|
|
}
|
2012-05-22 03:26:25 +00:00
|
|
|
|
|
|
|
|
function getArguments() {
|
|
|
|
|
return $this->args;
|
|
|
|
|
}
|
2008-06-26 13:05:40 +00:00
|
|
|
}
|
|
|
|
|
|
WARNING: HUGE COMMIT
Doxygen documentation update:
* Changed alls @addtogroup to @ingroup. @addtogroup adds the comment to the group description, but doesn't add the file, class, function, ... to the group like @ingroup does. See for example http://svn.wikimedia.org/doc/group__SpecialPage.html where it's impossible to see related files, classes, ... that should belong to that group.
* Added @file to file description, it seems that it should be explicitely decalred for file descriptions, otherwise doxygen will think that the comment document the first class, variabled, function, ... that is in that file.
* Removed some empty comments
* Removed some ?>
Added following groups:
* ExternalStorage
* JobQueue
* MaintenanceLanguage
One more thing: there are still a lot of warnings when generating the doc.
2008-05-20 17:13:28 +00:00
|
|
|
/**
|
|
|
|
|
* @ingroup Parser
|
|
|
|
|
*/
|
2008-02-05 08:23:58 +00:00
|
|
|
class PPNode_Hash_Tree implements PPNode {
|
|
|
|
|
var $name, $firstChild, $lastChild, $nextSibling;
|
|
|
|
|
|
|
|
|
|
function __construct( $name ) {
|
|
|
|
|
$this->name = $name;
|
|
|
|
|
$this->firstChild = $this->lastChild = $this->nextSibling = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function __toString() {
|
|
|
|
|
$inner = '';
|
|
|
|
|
$attribs = '';
|
|
|
|
|
for ( $node = $this->firstChild; $node; $node = $node->nextSibling ) {
|
|
|
|
|
if ( $node instanceof PPNode_Hash_Attr ) {
|
|
|
|
|
$attribs .= ' ' . $node->name . '="' . htmlspecialchars( $node->value ) . '"';
|
|
|
|
|
} else {
|
|
|
|
|
$inner .= $node->__toString();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ( $inner === '' ) {
|
|
|
|
|
return "<{$this->name}$attribs/>";
|
|
|
|
|
} else {
|
|
|
|
|
return "<{$this->name}$attribs>$inner</{$this->name}>";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-05-29 14:01:47 +00:00
|
|
|
/**
|
|
|
|
|
* @param $name
|
|
|
|
|
* @param $text
|
|
|
|
|
* @return PPNode_Hash_Tree
|
|
|
|
|
*/
|
2008-05-06 01:08:45 +00:00
|
|
|
static function newWithText( $name, $text ) {
|
2008-02-05 08:23:58 +00:00
|
|
|
$obj = new self( $name );
|
|
|
|
|
$obj->addChild( new PPNode_Hash_Text( $text ) );
|
|
|
|
|
return $obj;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function addChild( $node ) {
|
|
|
|
|
if ( $this->lastChild === false ) {
|
|
|
|
|
$this->firstChild = $this->lastChild = $node;
|
|
|
|
|
} else {
|
|
|
|
|
$this->lastChild->nextSibling = $node;
|
|
|
|
|
$this->lastChild = $node;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-05-29 14:01:47 +00:00
|
|
|
/**
|
|
|
|
|
* @return PPNode_Hash_Array
|
|
|
|
|
*/
|
2008-02-05 08:23:58 +00:00
|
|
|
function getChildren() {
|
|
|
|
|
$children = array();
|
|
|
|
|
for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
|
|
|
|
|
$children[] = $child;
|
|
|
|
|
}
|
|
|
|
|
return new PPNode_Hash_Array( $children );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getFirstChild() {
|
|
|
|
|
return $this->firstChild;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getNextSibling() {
|
|
|
|
|
return $this->nextSibling;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getChildrenOfType( $name ) {
|
|
|
|
|
$children = array();
|
|
|
|
|
for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
|
|
|
|
|
if ( isset( $child->name ) && $child->name === $name ) {
|
2012-08-05 20:14:45 +00:00
|
|
|
$children[] = $child;
|
2008-02-05 08:23:58 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return $children;
|
|
|
|
|
}
|
|
|
|
|
|
2011-05-29 14:01:47 +00:00
|
|
|
/**
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
function getLength() {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param $i
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
function item( $i ) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2008-02-05 08:23:58 +00:00
|
|
|
|
2011-05-29 14:01:47 +00:00
|
|
|
/**
|
|
|
|
|
* @return string
|
|
|
|
|
*/
|
2008-02-05 08:23:58 +00:00
|
|
|
function getName() {
|
|
|
|
|
return $this->name;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2012-07-10 12:48:06 +00:00
|
|
|
* Split a "<part>" node into an associative array containing:
|
|
|
|
|
* - name PPNode name
|
|
|
|
|
* - index String index
|
|
|
|
|
* - value PPNode value
|
2011-05-29 14:01:47 +00:00
|
|
|
*
|
2012-10-07 23:35:26 +00:00
|
|
|
* @throws MWException
|
2011-05-29 14:01:47 +00:00
|
|
|
* @return array
|
2008-02-05 08:23:58 +00:00
|
|
|
*/
|
|
|
|
|
function splitArg() {
|
|
|
|
|
$bits = array();
|
|
|
|
|
for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
|
|
|
|
|
if ( !isset( $child->name ) ) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if ( $child->name === 'name' ) {
|
|
|
|
|
$bits['name'] = $child;
|
2008-04-14 07:45:50 +00:00
|
|
|
if ( $child->firstChild instanceof PPNode_Hash_Attr
|
2008-02-05 08:23:58 +00:00
|
|
|
&& $child->firstChild->name === 'index' )
|
|
|
|
|
{
|
|
|
|
|
$bits['index'] = $child->firstChild->value;
|
|
|
|
|
}
|
|
|
|
|
} elseif ( $child->name === 'value' ) {
|
|
|
|
|
$bits['value'] = $child;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( !isset( $bits['name'] ) ) {
|
|
|
|
|
throw new MWException( 'Invalid brace node passed to ' . __METHOD__ );
|
|
|
|
|
}
|
|
|
|
|
if ( !isset( $bits['index'] ) ) {
|
|
|
|
|
$bits['index'] = '';
|
|
|
|
|
}
|
|
|
|
|
return $bits;
|
|
|
|
|
}
|
2008-04-14 07:45:50 +00:00
|
|
|
|
2008-02-05 08:23:58 +00:00
|
|
|
/**
|
2012-07-10 12:48:06 +00:00
|
|
|
* Split an "<ext>" node into an associative array containing name, attr, inner and close
|
2008-02-05 08:23:58 +00:00
|
|
|
* All values in the resulting array are PPNodes. Inner and close are optional.
|
2011-05-29 14:01:47 +00:00
|
|
|
*
|
2012-10-07 23:35:26 +00:00
|
|
|
* @throws MWException
|
2011-05-29 14:01:47 +00:00
|
|
|
* @return array
|
2008-02-05 08:23:58 +00:00
|
|
|
*/
|
|
|
|
|
function splitExt() {
|
|
|
|
|
$bits = array();
|
|
|
|
|
for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
|
|
|
|
|
if ( !isset( $child->name ) ) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if ( $child->name == 'name' ) {
|
|
|
|
|
$bits['name'] = $child;
|
|
|
|
|
} elseif ( $child->name == 'attr' ) {
|
|
|
|
|
$bits['attr'] = $child;
|
|
|
|
|
} elseif ( $child->name == 'inner' ) {
|
|
|
|
|
$bits['inner'] = $child;
|
|
|
|
|
} elseif ( $child->name == 'close' ) {
|
|
|
|
|
$bits['close'] = $child;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ( !isset( $bits['name'] ) ) {
|
|
|
|
|
throw new MWException( 'Invalid ext node passed to ' . __METHOD__ );
|
|
|
|
|
}
|
|
|
|
|
return $bits;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2012-07-10 12:48:06 +00:00
|
|
|
* Split an "<h>" node
|
2011-05-29 14:01:47 +00:00
|
|
|
*
|
2012-10-07 23:35:26 +00:00
|
|
|
* @throws MWException
|
2011-05-29 14:01:47 +00:00
|
|
|
* @return array
|
2008-02-05 08:23:58 +00:00
|
|
|
*/
|
|
|
|
|
function splitHeading() {
|
|
|
|
|
if ( $this->name !== 'h' ) {
|
|
|
|
|
throw new MWException( 'Invalid h node passed to ' . __METHOD__ );
|
|
|
|
|
}
|
|
|
|
|
$bits = array();
|
|
|
|
|
for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
|
|
|
|
|
if ( !isset( $child->name ) ) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if ( $child->name == 'i' ) {
|
|
|
|
|
$bits['i'] = $child->value;
|
|
|
|
|
} elseif ( $child->name == 'level' ) {
|
|
|
|
|
$bits['level'] = $child->value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ( !isset( $bits['i'] ) ) {
|
|
|
|
|
throw new MWException( 'Invalid h node passed to ' . __METHOD__ );
|
|
|
|
|
}
|
|
|
|
|
return $bits;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2012-07-10 12:48:06 +00:00
|
|
|
* Split a "<template>" or "<tplarg>" node
|
2011-05-29 14:01:47 +00:00
|
|
|
*
|
2012-10-07 23:35:26 +00:00
|
|
|
* @throws MWException
|
2011-05-29 14:01:47 +00:00
|
|
|
* @return array
|
2008-02-05 08:23:58 +00:00
|
|
|
*/
|
|
|
|
|
function splitTemplate() {
|
|
|
|
|
$parts = array();
|
|
|
|
|
$bits = array( 'lineStart' => '' );
|
|
|
|
|
for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
|
|
|
|
|
if ( !isset( $child->name ) ) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if ( $child->name == 'title' ) {
|
|
|
|
|
$bits['title'] = $child;
|
|
|
|
|
}
|
|
|
|
|
if ( $child->name == 'part' ) {
|
|
|
|
|
$parts[] = $child;
|
|
|
|
|
}
|
|
|
|
|
if ( $child->name == 'lineStart' ) {
|
|
|
|
|
$bits['lineStart'] = '1';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if ( !isset( $bits['title'] ) ) {
|
|
|
|
|
throw new MWException( 'Invalid node passed to ' . __METHOD__ );
|
|
|
|
|
}
|
|
|
|
|
$bits['parts'] = new PPNode_Hash_Array( $parts );
|
|
|
|
|
return $bits;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
WARNING: HUGE COMMIT
Doxygen documentation update:
* Changed alls @addtogroup to @ingroup. @addtogroup adds the comment to the group description, but doesn't add the file, class, function, ... to the group like @ingroup does. See for example http://svn.wikimedia.org/doc/group__SpecialPage.html where it's impossible to see related files, classes, ... that should belong to that group.
* Added @file to file description, it seems that it should be explicitely decalred for file descriptions, otherwise doxygen will think that the comment document the first class, variabled, function, ... that is in that file.
* Removed some empty comments
* Removed some ?>
Added following groups:
* ExternalStorage
* JobQueue
* MaintenanceLanguage
One more thing: there are still a lot of warnings when generating the doc.
2008-05-20 17:13:28 +00:00
|
|
|
/**
|
|
|
|
|
* @ingroup Parser
|
|
|
|
|
*/
|
2008-02-05 08:23:58 +00:00
|
|
|
class PPNode_Hash_Text implements PPNode {
|
|
|
|
|
var $value, $nextSibling;
|
|
|
|
|
|
|
|
|
|
function __construct( $value ) {
|
|
|
|
|
if ( is_object( $value ) ) {
|
|
|
|
|
throw new MWException( __CLASS__ . ' given object instead of string' );
|
|
|
|
|
}
|
|
|
|
|
$this->value = $value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function __toString() {
|
|
|
|
|
return htmlspecialchars( $this->value );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getNextSibling() {
|
|
|
|
|
return $this->nextSibling;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getChildren() { return false; }
|
|
|
|
|
function getFirstChild() { return false; }
|
|
|
|
|
function getChildrenOfType( $name ) { return false; }
|
|
|
|
|
function getLength() { return false; }
|
|
|
|
|
function item( $i ) { return false; }
|
|
|
|
|
function getName() { return '#text'; }
|
|
|
|
|
function splitArg() { throw new MWException( __METHOD__ . ': not supported' ); }
|
|
|
|
|
function splitExt() { throw new MWException( __METHOD__ . ': not supported' ); }
|
|
|
|
|
function splitHeading() { throw new MWException( __METHOD__ . ': not supported' ); }
|
|
|
|
|
}
|
|
|
|
|
|
WARNING: HUGE COMMIT
Doxygen documentation update:
* Changed alls @addtogroup to @ingroup. @addtogroup adds the comment to the group description, but doesn't add the file, class, function, ... to the group like @ingroup does. See for example http://svn.wikimedia.org/doc/group__SpecialPage.html where it's impossible to see related files, classes, ... that should belong to that group.
* Added @file to file description, it seems that it should be explicitely decalred for file descriptions, otherwise doxygen will think that the comment document the first class, variabled, function, ... that is in that file.
* Removed some empty comments
* Removed some ?>
Added following groups:
* ExternalStorage
* JobQueue
* MaintenanceLanguage
One more thing: there are still a lot of warnings when generating the doc.
2008-05-20 17:13:28 +00:00
|
|
|
/**
|
|
|
|
|
* @ingroup Parser
|
|
|
|
|
*/
|
2008-02-05 08:23:58 +00:00
|
|
|
class PPNode_Hash_Array implements PPNode {
|
|
|
|
|
var $value, $nextSibling;
|
|
|
|
|
|
|
|
|
|
function __construct( $value ) {
|
|
|
|
|
$this->value = $value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function __toString() {
|
|
|
|
|
return var_export( $this, true );
|
|
|
|
|
}
|
|
|
|
|
|
2008-04-14 07:45:50 +00:00
|
|
|
function getLength() {
|
|
|
|
|
return count( $this->value );
|
2008-02-05 08:23:58 +00:00
|
|
|
}
|
|
|
|
|
|
2008-04-14 07:45:50 +00:00
|
|
|
function item( $i ) {
|
|
|
|
|
return $this->value[$i];
|
2008-02-05 08:23:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getName() { return '#nodelist'; }
|
|
|
|
|
|
|
|
|
|
function getNextSibling() {
|
|
|
|
|
return $this->nextSibling;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getChildren() { return false; }
|
|
|
|
|
function getFirstChild() { return false; }
|
|
|
|
|
function getChildrenOfType( $name ) { return false; }
|
|
|
|
|
function splitArg() { throw new MWException( __METHOD__ . ': not supported' ); }
|
|
|
|
|
function splitExt() { throw new MWException( __METHOD__ . ': not supported' ); }
|
|
|
|
|
function splitHeading() { throw new MWException( __METHOD__ . ': not supported' ); }
|
|
|
|
|
}
|
|
|
|
|
|
WARNING: HUGE COMMIT
Doxygen documentation update:
* Changed alls @addtogroup to @ingroup. @addtogroup adds the comment to the group description, but doesn't add the file, class, function, ... to the group like @ingroup does. See for example http://svn.wikimedia.org/doc/group__SpecialPage.html where it's impossible to see related files, classes, ... that should belong to that group.
* Added @file to file description, it seems that it should be explicitely decalred for file descriptions, otherwise doxygen will think that the comment document the first class, variabled, function, ... that is in that file.
* Removed some empty comments
* Removed some ?>
Added following groups:
* ExternalStorage
* JobQueue
* MaintenanceLanguage
One more thing: there are still a lot of warnings when generating the doc.
2008-05-20 17:13:28 +00:00
|
|
|
/**
|
|
|
|
|
* @ingroup Parser
|
|
|
|
|
*/
|
2008-02-05 08:23:58 +00:00
|
|
|
class PPNode_Hash_Attr implements PPNode {
|
|
|
|
|
var $name, $value, $nextSibling;
|
|
|
|
|
|
|
|
|
|
function __construct( $name, $value ) {
|
|
|
|
|
$this->name = $name;
|
|
|
|
|
$this->value = $value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function __toString() {
|
|
|
|
|
return "<@{$this->name}>" . htmlspecialchars( $this->value ) . "</@{$this->name}>";
|
|
|
|
|
}
|
|
|
|
|
|
2008-04-14 07:45:50 +00:00
|
|
|
function getName() {
|
2008-02-05 08:23:58 +00:00
|
|
|
return $this->name;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getNextSibling() {
|
|
|
|
|
return $this->nextSibling;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getChildren() { return false; }
|
|
|
|
|
function getFirstChild() { return false; }
|
|
|
|
|
function getChildrenOfType( $name ) { return false; }
|
|
|
|
|
function getLength() { return false; }
|
|
|
|
|
function item( $i ) { return false; }
|
|
|
|
|
function splitArg() { throw new MWException( __METHOD__ . ': not supported' ); }
|
|
|
|
|
function splitExt() { throw new MWException( __METHOD__ . ': not supported' ); }
|
|
|
|
|
function splitHeading() { throw new MWException( __METHOD__ . ': not supported' ); }
|
|
|
|
|
}
|