Extension:Loops
From MediaWiki.org
|
Release status: stable |
|||
|---|---|---|---|
| Implementation | Parser function | ||
| Description | Loops, man! LOOPS! | ||
| Author(s) | David M. Sledge (talk) | ||
| Last Version | 0.3.0 (2009-05-02) | ||
| MediaWiki | 1.14.0 and later | ||
| License | GNU GPL 2.0 or later | ||
| Download | See below | ||
|
|||
|
|||
|
check usage (experimental) |
|||
Contents |
[edit] What can this extension do?
While and do-while loops similar to those used in programming languages.
[edit] Usage
[edit] Syntax
For technical reasons, the preprocessor in v1.12alpha and later handles the first parameter differently than the rest, so it's ignored for the #while and #dowhile parser functions in order for them to work. See bug 12842.
[edit] #while
{{#while}} performs a loop (i.e. it repeatedly parses a given wiki markup block statement) so long as the condition mark-up evaluates to non-whitespace.
{{
#while:
| <condition text>
| <block statement>
}}
[edit] Examples
Note: The following examples use the VariablesExtension.
The wiki markup:
{{ #vardefine: i | 0 }}{{
#while:
| {{ #ifexpr: {{ #var: i }} < 5 | true }}
|<nowiki/>
* {{ #var: i }}{{ #vardefine: i | {{ #expr: {{ #var: i }} + 1 }} }}
}}
produces the following:
- 0
- 1
- 2
- 3
- 4
{{#while}} can also be used in a template to simulate a numbered array. If the page "Template:Loops Test" contains
{{
#vardefine: i | 0
}}{{
#while:
| {{{ arg{{#var: i }} |}}}
|<nowiki/>
* {{{ arg{{#var: i }} }}}{{
#vardefine: i
| {{ #expr: {{ #var: i }} + 1 }}
}}
}}
then the wiki-markup
{{Loops Test
|arg0=zero
|arg1=one
|arg2=two
|arg3=three
|arg4=four
}}
produces
- zero
- one
- two
- three
- four
It's important to note that whitespace, including newlines, tabs, and spaces, is stripped from the beginning and end of all the arguments of these parser functions. If this is not desirable, adding any non-whitespace characters (including the HTML encoding for a whitespace character  ) will prevent further stripping (hence the <nowiki/> tags in the above examples).
[edit] #dowhile
{{#dowhile}} performs exactly like {{#while}}, with the exception that the block statement is guaranteed to be parsed and displayed (if it results in displayable text) at least once. This is done before the condition text is evaluated.
[edit] #loop (Experimental)
{{
#loop:
| <variable name>
| <starting value>
| <number of loops to be performed>
| <wiki markup>
}}
{{#loop}} repeatedly parses and displays <wiki markup> a number of times equal to the absolute value of <number of loops to be performed>. <Starting value> is placed in a variable (accessible by VariablesExtension's {{#var:}} parser function) using the name <variable name>. After each loop, the variable is incremented by one if <number of loops to be performed> is positive, or decremented by one if <number of loops to be performed> is negative.
[edit] Examples
TBD
[edit] #forargs (Experimental)
{{#forargs}} is to be used in templates. It takes arguments that are passed to the template and puts them in variables accessible by VariablesExtension's {{#var:}} parser function.
{{
#forargs: <prefix>
| <key>
| <value>
| <block statement>
}}
This function iterates through each argument whose name begins with <prefix>. With each iteration it puts the argument name minus <prefix> into <key> as if calling {{#vardefine: <key> }}. It then takes the value of the argument and puts it into <value> in a similar method. The block statement is then expanded. The block statement may contain {{#var: <key> }} and {{#var: <value> }} to access the stored arguments.
[edit] Example
If the page "Template:Loops Test" contains
{{
#forargs: arg
| key
| value
| <nowiki/>
* {{#var: key}} = {{#var: value}}
}}
then the wiki markup
{{Loops Test
| arg1=val1
| spam=spammity
| arg5=val5
| argument=value
}}
produces
- 1 = val1
- 5 = val5
- ument = value
[edit] #fornumargs (Experimental)
{{
#fornumargs: <key>
| <value>
| <block statement>
}}
{{#fornumargs}} performs similarly to {{#forargs}} with two major differences: It doesn't take a prefix argument, and it only works on numbered arguments whether they're explicitly numbered:
{{
Tplate
| 1=one
}}
or implicitly numbered:
{{
Tplate
| one
}}
[edit] Examples
TBD
[edit] Download instructions
Please cut and paste the code found below and place it in $IP/extensions/Loops/Loops.php. Note: $IP stands for the root directory of your MediaWiki installation, the same directory that holds LocalSettings.php.
[edit] Installation
The parser functions #loop, #forargs, and #fornumargs require VariablesExtension to be installed. Loops is installed by adding the following to LocalSettings.php:
require_once( "$IP/extensions/Loops/Loops.php" );
[edit] Configuration parameters
[edit] ExtLoops::$maxLoops
This parameter sets the maximum number of loops a page is allowed to perform. Setting it to a negative value lets the loops run within the limits of php's environment. This parameter affects neither the {{#forargs:}} nor {{#fornumargs:}}.
[edit] Code
[edit] Loops.php
<?php /** * @package MediaWiki * @subpackage Extensions * * @link http://www.mediawiki.org/wiki/Extension:Loops Documentation * * @author David M. Sledge * @copyright Copyright © 2008 David M. Sledge * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 * or later * @version 0.1.0 * initial creation. * @version 0.2.0 * #foreachnamedarg added * @version 0.3.0 * technical change from static members and methods to singleton * #loop added * #fornumargs added * #foreachnamedarg renamed to #forargs expaned to include indexed template * arguments as well as named arguments * * @todo message notifying the need for the VariablesExtension extension for * certain functions * @todo inline documentation */ if ( !defined( "MEDIAWIKI" ) ) { die( "This file is a MediaWiki extension, it is not a valid entry point" ); } $wgHooks[ 'ParserFirstCallInit' ][] = $wgHooks[ 'LanguageGetMagic' ][] = $wgHooks[ 'ParserLimitReport' ][] = ExtLoops::getInstance(); $wgExtensionCredits[ 'parserhook' ][] = array( 'path' => __FILE__, "author" => "David M. Sledge", "name" => "Loops", "version" => ExtLoops::VERSION, "description" => "Parser functions for performing loops", "url" => "http://www.mediawiki.org/wiki/Extension:Loops", ); class ExtLoops { const VERSION = "0.3.0"; public static $maxLoops = 100; // maximum number of loops allowed // (-1 = no limit). #forargs is // not limited by this. private static $instance = null; private $parserFunctions = array( 'dowhile' => array( 'dowhile', SFH_OBJECT_ARGS ), 'while' => array( 'whileHook', SFH_OBJECT_ARGS ), 'loop' => array( 'loop', SFH_OBJECT_ARGS ), 'forargs' => array( 'forArgs', SFH_OBJECT_ARGS ), 'fornumargs' => array( 'forNumArgs', SFH_OBJECT_ARGS ), ); private $loopCount = 0; public static function getInstance() { // create the singleton if needed if ( self::$instance === null ) self::$instance = new self(); return self::$instance; } /** * limited-access constructor to insure singleton */ protected function __construct() { } public function onParserFirstCallInit( &$parser ) { global $wgMessageCache, $wgHooks; // These functions accept DOM-style arguments foreach( $this->parserFunctions as $hook => $callAndFlags ) $parser->setFunctionHook( $hook, array( $this, $callAndFlags[ 0 ] ), $callAndFlags[ 1 ] ); require_once( dirname( __FILE__ ) . '/Loops.i18n.php' ); foreach( Loops_i18n::getInstance()->getMessages() as $lang => $messages ) $wgMessageCache->addMessages( $messages, $lang ); $wgHooks[ 'ParserClearState' ][] = $this; return true; } public function onLanguageGetMagic( &$magicWords, $langCode ) { require_once( dirname( __FILE__ ) . '/Loops.i18n.php' ); foreach( Loops_i18n::getInstance()->magicWords( $langCode ) as $word => $trans ) $magicWords[ $word ] = $trans; return true; } public function onParserLimitReport( $parser, &$report ) { if ( isset( $this->loopCount ) ) { $report .= "ExtLoops count: {$this->loopCount}/" . self::$maxLoops . "\n"; } return true; } public function whileHook( &$parser, $frame, $args ) { // bug 12842: first argument is automatically // expanded, so we ignore this one array_shift( $args ); $test = array_shift( $args ); $loopStatement = array_shift( $args ); $output = ''; while ( isset( $test ) && trim( $frame->expand( $test ) ) !== '' ) { if ( self::$maxLoops >= 0 && ++$this->loopCount > self::$maxLoops ) return $output . wfMsgForContent( 'loops_max' ); $output .= isset( $loopStatement ) ? trim( $frame->expand( $loopStatement ) ) : ''; } //return '<pre><nowiki>'. $output . '</nowiki></'. 'pre>'; return $output; } public function dowhile( &$parser, $frame, $args ) { // bug 12842: first argument is automatically // expanded, so we ignore this one array_shift( $args ); $test = array_shift( $args ); $loopStatement = array_shift( $args ); $output = ''; do { if ( self::$maxLoops >= 0 && ++$this->loopCount > self::$maxLoops ) return $output . wfMsgForContent( 'loops_max' ); $output .= isset( $loopStatement ) ? trim( $frame->expand( $loopStatement ) ) : ''; } while ( isset( $test ) && trim( $frame->expand( $test ) ) !== '' ); //return '<pre><nowiki>'. $output . '</nowiki></'. 'pre>'; return $output; } public function forArgs( &$parser, $frame, $args ) { if ( !( $frame instanceof PPTemplateFrame_DOM ) ) { $arg = array_shift( $args ); $arg = isset( $arg ) ? $frame->expand( $arg ) : ''; // TODO: get the synonym for the content language $out = "{{#forargs:$arg"; // expand and display each argument while ( ( $arg = array_shift( $args ) ) !== null ) { $out .= '|' . $frame->expand( $arg ); } $out .= '}}'; return $out; } global $wgExtVariables; // The first arg is already expanded, but this is a good habit to have. $filter = isset( $args[0] ) ? trim( $frame->expand( $args[0] ) ) : ''; // name of the variable to store the argument name. this // will be accessed in the loop by using {{#var:}} $keyVarName = isset( $args[1] ) ? trim( $frame->expand( $args[1] ) ) : ''; // name of the variable to store the argument value. $valueVarName = isset( $args[2] ) ? trim( $frame->expand( $args[2] ) ) : ''; $loopStatement = isset( $args[3] ) ? $args[3] : ''; $output = ''; $tArgs = preg_match( '/^([1-9][0-9]*)?$/', $filter ) > 0 ? $frame->getArguments() : $frame->getNamedArguments(); foreach ( $tArgs as $argName => $argVal ) { if ( $filter == '' || strpos( $argName, $filter ) === 0 ) { if ( $keyVarName !== $valueVarName ) $wgExtVariables->vardefine( $parser, $keyVarName, substr( $argName, strlen( $filter ) ) ); $wgExtVariables->vardefine( $parser, $valueVarName, $argVal ); $output .= trim( $frame->expand( $loopStatement ) ); } } return $output; } public function forNumArgs( &$parser, $frame, $args ) { if ( !( $frame instanceof PPTemplateFrame_DOM ) ) { $arg = array_shift( $args ); $arg = isset( $arg ) ? $frame->expand( $arg ) : ''; // TODO: get the synonym for the content language $out = "{{#forargs:$arg"; // expand and display each argument while ( ( $arg = array_shift( $args ) ) !== null ) { $out .= '|' . $frame->expand( $arg ); } $out .= '}}'; return $out; } global $wgExtVariables; // The first arg is already expanded, but this is a good habit to have. // name of the variable to store the argument name. this // will be accessed in the loop by using {{#var:}} $keyVarName = isset( $args[0] ) ? trim( $frame->expand( $args[0] ) ) : ''; // name of the variable to store the argument value. $valueVarName = isset( $args[1] ) ? trim( $frame->expand( $args[1] ) ) : ''; $loopStatement = isset( $args[2] ) ? $args[2] : ''; $output = ''; $numArgs = $frame->getArguments(); ksort( $numArgs ); foreach ( $numArgs as $argNumber => $argVal ) { if ( is_string( $argNumber ) ) continue; if ( $keyVarName !== $valueVarName ) $wgExtVariables->vardefine( $parser, $keyVarName, $argNumber ); $wgExtVariables->vardefine( $parser, $valueVarName, $argVal ); $output .= trim( $frame->expand( $loopStatement ) ); } return $output; } public function loop( &$parser, $frame, $args ) { global $wgExtVariables; // snag the variable name $varName = array_shift( $args ); $varName = $varName === null ? '' : trim( $frame->expand( $varName ) ); // grab the intitial value for the variable (default to 0) $startVal = array_shift( $args ); $startVal = $startVal === null ? 0 : intval( trim( $frame->expand( $startVal ) ) ); // How many times are we gonna loop? $count = array_shift( $args ); if ( $count === null ) return ''; $endVal = $startVal + intval( trim( $frame->expand( $count ) ) ); if ( $endVal == $startVal ) return ''; // grab the unexpanded loop statement $loopStatement = array_shift( $args ); $output = ''; for ( ; $startVal != $endVal; $startVal < $endVal ? $startVal++ : $startVal-- ) { if ( self::$maxLoops >= 0 && ++$this->loopCount > self::$maxLoops ) return $output . wfMsgForContent( 'loops_max' ); $wgExtVariables->vardefine( $parser, $varName, $startVal ); $output .= isset( $loopStatement ) ? trim( $frame->expand( $loopStatement ) ) : ''; } return $output; } public function onParserClearState( &$parser ) { $this->loopCount = 0; return true; } }
[edit] Loops.i18n.php
<?php class Loops_i18n { private $words = array( // English 'en' => array( 'dowhile' => array( 0, 'dowhile' ), 'while' => array( 0, 'while' ), 'forargs' => array( 0, 'forargs' ), 'fornumargs' => array( 0, 'fornumargs' ), 'loop' => array( 0, 'loop' ), ), ); private $messages = array( // English 'en' => array( 'loops_max' => "Maximum number of loops have been performed", ) ); private static $instance = null; public static function getInstance() { // create the singleton if needed if ( self::$instance === null ) self::$instance = new self(); return self::$instance; } /** * limited-access constructor to insure singleton */ protected function __construct() { } /** * Get translated magic words, if available * * @param string $lang Language code * @return array */ public function magicWords( $lang ) { // English is used as a fallback, and the English synonyms are // used if a translation has not been provided for a given word return ( $lang == 'en' || !isset( $this->words[ $lang ] ) ) ? $this->words[ 'en' ] : array_merge( $this->words[ 'en' ], $this->words[ $lang ] ); } public function getMessages() { return $this->messages; } }
[edit] See also
- Control Structure Functions for MediaWiki v1.11.x
- LoopFunctions
- VariablesExtension