Extension:LO Parser Functions

From MediaWiki.org
Jump to: navigation, search
MediaWiki extensions manualManual:Extensions
Crystal Clear action run.png
LO ParserFunctions

Release status:Extension status beta

ImplementationTemplate:Extension#type Parser extension, Parser function
DescriptionTemplate:Extension#description A collection of parser functions such as expression handling and time calculation unit.
Author(s)Template:Extension#username Tim Starling (this extraction from his ParserFunctions extension is maintained by David M. Sledge)
Latest versionTemplate:Extension#version N/A (2007-11-15)
MediaWikiTemplate:Extension#mediawiki > 1.6.8
LicenseTemplate:Extension#license GNU General Public License 2.0 or later
Download No link

Translate the LO Parser Functions extension if it is available at translatewiki.net

Check usage and version matrix.

This extension contains the parser functions from the ParserFunctions extension that aren't in the control structure functions extension; #expr, #time, and #rel2abs. See ParserFunctions for usage.

Installation[edit]

See Replacing ParserFunctions with Control Structure Functions before installing.

Download the file Expr.php and put it in a new directory called LOParserFunctions in your extensions directory. Then in the new directory, create the files LOParserFunctions.php and LOParserFunctions.i18n.php that contains the following source code:

LOParserFunctions.php:

<?php

if ( !defined( 'MEDIAWIKI' ) ) {
	die( 'This file is a MediaWiki extension, it is not a valid entry point' );
}

$wgExtensionFunctions[] = 'wfSetupLOParserFunctions';
$wgExtensionCredits['parserhook'][] = array(
	'name' => 'Left-Over Parser Functions',
	'url' => 'http://www.mediawiki.org/wiki/Extension:LO_Parser_Functions',
	'author' => 'Tim Starling',
	'description' => 'Enhance parser with logical functions',
);

$wgHooks['LanguageGetMagic'][]       = 'wfLOParserFunctionsLanguageGetMagic';

class ExtLOParserFunctions {
	var $mExprParser;
	var $mTimeCache = array();
	var $mTimeChars = 0;
	var $mMaxTimeChars = 6000; # ~10 seconds

	function clearState() {
		$this->mTimeChars = 0;
		return true;
	}

	function &getExprParser() {
		if ( !isset( $this->mExprParser ) ) {
			if ( !class_exists( 'ExprParser' ) ) {
				require( dirname( __FILE__ ) . '/Expr.php' );
				ExprParser::addMessages();
			}
			$this->mExprParser = new ExprParser;
		}
		return $this->mExprParser;
	}

	function expr( &$parser, $expr = '' ) {
		try {
			return $this->getExprParser()->doExpression( $expr );
		} catch(ExprError $e) {
			return $e->getMessage();
		}
	}

	/**
	 * Returns the absolute path to a subpage, relative to the current article
	 * title. Treats titles as slash-separated paths.
	 *
	 * Following subpage link syntax instead of standard path syntax, an 
	 * initial slash is treated as a relative path, and vice versa.
	 */
	public function rel2abs( &$parser , $to = '' , $from = '' ) {

		$from = trim($from);
		if( $from == '' ) {
			$from = $parser->getTitle()->getPrefixedText();
		}

		$to = rtrim( $to , ' /' );

		// if we have an empty path, or just one containing a dot
		if( $to == '' || $to == '.' ) {
			return $from;
		}

		// if the path isn't relative
		if ( substr( $to , 0 , 1) != '/' &&
		 substr( $to , 0 , 2) != './' &&
		 substr( $to , 0 , 3) != '../' &&
		 $to != '..' )
		{
			$from = '';
		}
		// Make a long path, containing both, enclose it in /.../
		$fullPath = '/' . $from . '/' .  $to . '/';

		// remove redundant current path dots
		$fullPath = preg_replace( '!/(\./)+!', '/', $fullPath );

		// remove double slashes
		$fullPath = preg_replace( '!/{2,}!', '/', $fullPath );

		// remove the enclosing slashes now
		$fullPath = trim( $fullPath , '/' );
		$exploded = explode ( '/' , $fullPath );
		$newExploded = array();

		foreach ( $exploded as $current ) {
			if( $current == '..' ) { // removing one level
				if( !count( $newExploded ) ){
					// attempted to access a node above root node
					return wfMsgForContent( 'pfunc_rel2abs_invalid_depth', $fullPath );
				}
				// remove last level from the stack
				array_pop( $newExploded );
			} else {
				// add the current level to the stack
				$newExploded[] = $current;
			}
		}

		// we can now join it again
		return implode( '/' , $newExploded );
	}

	function time( &$parser, $format = '', $date = '', $local = false ) {
		global $wgContLang, $wgLocaltimezone;
		if ( isset( $this->mTimeCache[$format][$date][$local] ) ) {
			return $this->mTimeCache[$format][$date][$local];
		}

		if ( $date !== '' ) {
			$unix = @strtotime( $date );
		} else {
			$unix = time();
		}

		if ( $unix == -1 || $unix == false ) {
			$result = wfMsgForContent( 'pfunc_time_error' );
		} else {
			$this->mTimeChars += strlen( $format );
			if ( $this->mTimeChars > $this->mMaxTimeChars ) {
				return wfMsgForContent( 'pfunc_time_too_long' );
			} else {
				if ( $local ) {
					# Use the time zone
					if ( isset( $wgLocaltimezone ) ) {
						$oldtz = getenv( 'TZ' );
						putenv( 'TZ='.$wgLocaltimezone );
					}
					wfSuppressWarnings(); // E_STRICT system time bitching
					$ts = date( 'YmdHis', $unix );
					wfRestoreWarnings();
					if ( isset( $wgLocaltimezone ) ) {
						putenv( 'TZ='.$oldtz );
					}
				} else {
					$ts = wfTimestamp( TS_MW, $unix );
				}
				if ( method_exists( $wgContLang, 'sprintfDate' ) ) {
					$result = $wgContLang->sprintfDate( $format, $ts );
				} else {
					if ( !class_exists( 'SprintfDateCompat' ) ) {
						require( dirname( __FILE__ ) . '/SprintfDateCompat.php' );
					}

					$result = SprintfDateCompat::sprintfDate( $format, $ts );
				}
			}
		}
		$this->mTimeCache[$format][$date][$local] = $result;
		return $result;
	}
	
	function localTime( &$parser, $format = '', $date = '' ) {
		return $this->time( $parser, $format, $date, true );
	}
	
	/**
	 * Obtain a specified number of slash-separated parts of a title,
	 * e.g. {{#titleparts:Hello/World|1}} => "Hello"
	 *
	 * @param Parser $parser Parent parser
	 * @param string $title Title to split
	 * @param int $parts Number of parts to keep
	 * @param int $offset Offset starting at 1
	 * @return string
	 */
	public function titleparts( $parser, $title = '', $parts = -1, $offset = 1 ) {
		$parts = intval( $parts );
		$offset = intval( $offset ) - 1;
		$ntitle = Title::newFromText( $title );
		if( $ntitle instanceof Title ) {
			$bits = explode( '/', $ntitle->getPrefixedText() );
			if( $parts <= 0 || $parts > count( $bits ) ) {
				return $ntitle->getPrefixedText();
			} elseif( $offset < 0 || $offset > count( $bits ) ) {
				return $ntitle->getPrefixedText();
			} else {
				$keep = array();
				for( $i = 0; $i < $offset; $i++ )
					array_shift( $bits );
				for( $i = 0; $i < $parts; $i++ )
					$keep[] = array_shift( $bits );
				return implode( '/', $keep );
			}
		} else {
			return $title;
		}
	}
}

function wfSetupLOParserFunctions() {
	global $wgParser, $wgMessageCache, $wgExtLOParserFunctions, $wgMessageCache, $wgHooks;

	$wgExtLOParserFunctions = new ExtLOParserFunctions;

	$wgParser->setFunctionHook( 'expr', array( &$wgExtLOParserFunctions, 'expr' ) );
	$wgParser->setFunctionHook( 'time', array( &$wgExtLOParserFunctions, 'time' ) );
	$wgParser->setFunctionHook( 'timel', array( &$wgExtLOParserFunctions, 'localTime' ) );
	$wgParser->setFunctionHook( 'rel2abs', array( &$wgExtLOParserFunctions, 'rel2abs' ) );
	$wgParser->setFunctionHook( 'titleparts', array( &$wgExtLOParserFunctions, 'titleparts' ) );
	
	require_once( dirname( __FILE__ ) . '/LOParserFunctions.i18n.php' );
	foreach( efLOParserFunctionsMessages() as $lang => $messages )
		$wgMessageCache->addMessages( $messages, $lang );

	$wgHooks['ParserClearState'][] = array( &$wgExtLOParserFunctions, 'clearState' );
}

function wfLOParserFunctionsLanguageGetMagic( &$magicWords, $langCode ) {
	require_once( dirname( __FILE__ ) . '/LOParserFunctions.i18n.php' );
	foreach( efLOParserFunctionsWords( $langCode ) as $word => $trans )
		$magicWords[$word] = $trans;
	return true;
}

LOParserFunctions.i18n.php:

<?php

/**
 * Get translated magic words, if available
 *
 * @param string $lang Language code
 * @return array
 */
function efLOParserFunctionsWords( $lang ) {
	$words = array();

	/**
	 * English
	 */
	$words['en'] = array(
		'expr' 		=> array( 0, 'expr' ),
		'time' 		=> array( 0, 'time' ),
		'timel' 	=> array( 0, 'timel' ),
		'rel2abs' 	=> array( 0, 'rel2abs' ),
		'titleparts' => array( 0, 'titleparts' ),
	);

	/**
	 * Farsi-Persian
	 */
	$words['fa'] = array(
		'expr' 		=> array( 0, 'حساب',         'expr' ),
		'time' 		=> array( 0, 'زمان',         'time' ),
		'rel2abs' 	=> array( 0, 'نسبی‌به‌مطلق',   'rel2abs' ),
	);

	/**
	 * Hebrew
	 */
	$words['he'] = array(
		'expr'       => array( 0, 'חשב',         'expr' ),
		'time'       => array( 0, 'זמן',         'time' ),
		'timel'      => array( 0, 'זמןמ',        'timel' ),
		'rel2abs'    => array( 0, 'יחסי למוחלט', 'rel2abs' ),
		'titleparts' => array( 0, 'חלק בכותרת',  'titleparts' ),
	);

	/**
	 * Indonesian
	 */
	$words['id'] = array(
		'expr'       => array( 0, 'hitung',       'expr' ),
		'time'       => array( 0, 'waktu',        'time' ),
		'rel2abs'    => array( 0, 'rel2abs' ),
		'titleparts' => array( 0, 'bagianjudul',  'titleparts' ),
	);

	# 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( $words[$lang] ) )
		? $words['en']
		: array_merge( $words['en'], $words[$lang] );
}

/**
 * Get extension messages
 *
 * @return array
 */
function efLOParserFunctionsMessages() {
	$messages = array(

/* English */
'en' => array(
	 'pfunc_time_error'             => 'Error: invalid time',
	 'pfunc_time_too_long'          => 'Error: too many #time calls',
	 'pfunc_rel2abs_invalid_depth'  => 'Error: Invalid depth in path: \"$1\" (tried to access a node above the root node)',
),

/* German */
'de' => array(
	 'pfunc_time_error'             => 'Fehler: ungültige Zeitangabe',
	 'pfunc_time_too_long'          => 'Fehler: zu viele #time-Aufrufe',
	 'pfunc_rel2abs_invalid_depth'  => 'Fehler: ungültige Tiefe in Pfad: „$1“ (Versuch, auf einen Knotenpunkt oberhalb des Hauptknotenpunktes zuzugreifen)',
),

/* French */
'fr' => array(
	 'pfunc_time_error'            => 'Erreur: durée invalide',
	 'pfunc_time_too_long'         => 'Erreur: parser #time appelé trop de fois',
	 'pfunc_rel2abs_invalid_depth' => 'Erreur: niveau de répertoire invalide dans le chemin : \"$1\" (a essayé d’accéder à un niveau au-dessus du répertoire racine)',
),

/* Hebrew */
'he' => array(
	 'pfunc_time_error'             => 'שגי×?×”: זמן שגוי',
	 'pfunc_time_too_long'          => 'שגי×?×”: שימוש ב"#זמן" פעמי×? רבות מדי',
	 'pfunc_rel2abs_invalid_depth'  => 'שגי×?×”: עומק שגוי ×‘× ×ª×™×‘: \"$1\" (× ×™×¡×™×•×Ÿ ×›× ×™×¡×” לצומת מעל צומת השורש)',
),

/* Kazakh Cyrillic */
'kk-kz' => array(
	 'pfunc_time_error'             => 'Қате: жарамÑ?ыз уақыт',
	 'pfunc_time_too_long'          => 'Қате: #time әмірін шақыруы тым көп',
	 'pfunc_rel2abs_invalid_depth'  => 'Қате: Мына жолдың жарамÑ?ыз терендігі "$1" (тамыр түйіннің Ò¯Ñ?тіндегі түйінге қатынау талабы)',
),
/* Kazakh Latin */
'kk-tr' => array(
	 'pfunc_time_error'             => 'Qate: jaramsız waqıt',
	 'pfunc_time_too_long'          => 'Qate: #time ämirin şaqırwı tım köp',
	 'pfunc_rel2abs_invalid_depth'  => 'Qate: Mına joldıñ jaramsız terendigi "$1" (tamır tüýinniñ üstindegi tüýinge qatınaw talabı)',
),
/* Kazakh Arabic */
'kk-cn' => array(
	 'pfunc_time_error'             => 'قاتە: جارامسىز ۋاقىت',
	 'pfunc_time_too_long'          => 'قاتە: #time ٵمٸرٸن شاقىرۋى تىم كٶپ',
	 'pfunc_rel2abs_invalid_depth'  => 'قاتە: مىنا جولدىڭ جارامسىز تەرەندٸگٸ "$1" (تامىر تٷيٸننٸڭ ٷستٸندەگٸ تٷيٸنگە قاتىناۋ تالابى)',
),

/* Dutch */
'nl' => array(
	 'pfunc_time_error'             => 'Fout: ongeldige tijd',
	 'pfunc_time_too_long'          => 'Fout: #time te vaak aangeroepen',
	 'pfunc_rel2abs_invalid_depth'  => 'Fout: ongeldige diepte in pad: \"$1\" (probeerde een node boven de stamnode aan te roepen)',
),

/* Swedish */
'sv' => array(
	 'pfunc_time_error'             => 'Fel: ogiltig tid',
	 'pfunc_time_too_long'          => 'Fel: för många anrop av #time',
	 'pfunc_rel2abs_invalid_depth'  => 'Fel: felaktig djup i sökväg: "$1" (försöker nå en nod ovanför rotnoden)',
),

/* Cantonese */
'yue' => array(
	 'pfunc_time_error'             => '錯: 唔啱嘅時間',
	 'pfunc_time_too_long'          => '錯: 太多 #time 呼�',
	 'pfunc_rel2abs_invalid_depth'  => '錯: 唔啱路徑嘅深度: \"$1\" (已經試é?Žç”±é ­é»žè?½å€‹é»žåº¦)',
),

/* Chinese (Simplified) */
'zh-hans' => array(
	 'pfunc_time_error'             => '错误: �正确的时间',
	 'pfunc_time_too_long'          => '错误: 过多 #time 的呼�',
	 'pfunc_rel2abs_invalid_depth'  => '错误: �正确的路径深度: \"$1\" (已��试在顶点访问该点)',
),


/* Chinese (Traditional) */
'zh-hant' => array(
	 'pfunc_time_error'             => '錯誤: �正確的時間',
	 'pfunc_time_too_long'          => '錯誤: �多 #time 的呼�',
	 'pfunc_rel2abs_invalid_depth'  => '錯誤: ä¸?正確的路徑深度: \"$1\" (å·²ç¶“å˜—è©¦åœ¨é ‚é»žå­˜å?–該點)',
),

);

	/* Kazakh default, fallback to kk-kz */
	$messages['kk'] = $messages['kk-kz'];

	/* Chinese defaults, fallback to zh-hans */
	$messages['zh'] = $messages['zh-hans'];
	$messages['zh-cn'] = $messages['zh-hans'];
	$messages['zh-hk'] = $messages['zh-hant'];
	$messages['zh-sg'] = $messages['zh-hans'];
	$messages['zh-tw'] = $messages['zh-hant'];

	/* Cantonese default, fallback to yue */
	$messages['zh-yue'] = $messages['yue'];

	return $messages ;
}

Then put the following at the end of your LocalSettings.php:

require_once( "$IP/extensions/LOParserFunctions/LOParserFunctions.php" );

See Also[edit]