(bug 235) parser function for conversion of units of measurement.

[[Template:Convert]] on enwiki is a behemoth of a construction that just about manages to do this sort of conversion, taking {{convert|5|mi|km}} and outputting "5 miles (8 km)", etc.  To port this to another wiki requires copying over three and a half thousand subtemplates.  The additional load produced by including numerous copies of this template is measurable on large pages on enwiki, and it eats voraciously into the template limits.

This revision introduces {{#convert: 5 mi | km }}, outputting "8 km" or thereabouts.  See http://www.mediawiki.org/wiki/User:Happy-melon/Convert for more details, or look at the examples in the parser tests.  

In a very rough profile, comparing 50 calls to {{convert}} verses the same 50 calls to the wrapper template shown at the link above, the parser function implementation reduces page load time by 72%, preprocessor node count by 83%, post-expand include size by 86% and template argument size by 97%.  More detailed profiling would probably reveal places where extra caching could improve performance further.

The primary reason for putting it in ParserFunctions instead of its own extension is availability: PFs are already available across the cluster, and it's accepted as an essential extension for any wiki wishing to emulate or mirror WMF content.  One less separate extension installed on the cluster is one less extension which has to be matched by reusers.  

It's still missing a lot of units, which I ran out of patience to copy from {{convert}}; I thought I'd get some feedback on the infrastructure first.
This commit is contained in:
Happy-melon 2011-01-27 00:13:10 +00:00
parent e512f7f3cd
commit 65e9e69202
6 changed files with 1198 additions and 1 deletions

735
Convert.php Normal file
View file

@ -0,0 +1,735 @@
<?php
if ( !defined( 'MEDIAWIKI' ) ) {
die( 'This file is a MediaWiki extension, it is not a valid entry point' );
}
class ConvertError extends Exception {
public function __construct( $msg /*...*/ ) {
$args = func_get_args();
array_shift( $args );
$this->message = '<strong class="error">' . wfMsgExt( "pfunc-convert-$msg", 'parseinline', $args ) . '</strong>';
}
}
class ConvertParser {
# A regex which matches the body of the string and the source unit separately
const UNITS_REGEX = '/^(.+?)\s*([a-z]+\^?\d?(?:\/\w+\^?\d?)*)$/i';
# A regex which matches a number
const NUM_REGEX = '/\b((?:\+|\-|&minus;|\x2212)?(\d+(?:\.\d+)?)(?:E(?:\+|\-|&minus;|\x2212)?\d+)?)\b/i';
# ConvertUnit objects
protected $sourceUnit;
protected $targetUnit;
# Whether to abbreviate the output unit
protected $abbreviate;
# Whether to link the output unit, if possible
protected $link;
# If set, don't output the unit or format the number
protected $raw;
# What precision to round to.
protected $decimalPlaces;
protected $significantFigures;
# The last value converted, which will be used for PLURAL evaluation
protected $lastValue;
public function clearState(){
# Make sure we break any references set up in the parameter passing below
unset( $this->sourceUnit );
unset( $this->targetUnit );
$this->sourceUnit = null;
$this->targetUnit = null;
$this->lastValue
= $this->link
= $this->precision
= $this->abbreviate
= $this->raw
= $this->significantFigures
= $this->decimalPlaces
= null;
}
/**
* Evaluate a convert expression
* @param $args Array of the parameters passed to the original tag function
* @return String
* @throws ConvertError
*/
public function execute( $args ) {
$this->clearState();
array_shift( $args ); # Dump Parser object
if( count( $args ) == 0 ){
# that was easy
return '';
}
$string = trim( array_shift( $args ) );
# Process the rest of the args
$sourceUnit =& MagicWord::get( 'sourceunit' );
$targetUnit =& MagicWord::get( 'targetunit' );
$linkUnit =& MagicWord::get( 'linkunit' );
$dp =& MagicWord::get( 'decimalplaces' );
$sf =& MagicWord::get( 'significantfigures' );
$abbr =& MagicWord::get( 'abbreviate' );
$raw =& MagicWord::get( 'rawsuffix' );
$n = 0; # Count of unnamed parameters
foreach ( $args as $arg ) {
$parts = array_map( 'trim', explode( '=', $arg, 2 ) );
if ( count( $parts ) == 2 ) {
# Found "="
if ( $sourceUnit->matchStartAndRemove( $parts[0] ) ) {
if( $targetUnit->matchStartAndRemove( $parts[1] ) ){
$this->targetUnit =& $this->sourceUnit;
} else {
$this->sourceUnit = new ConvertUnit( $parts[1] );
}
} elseif ( $targetUnit->matchStartAndRemove( $parts[0] ) ) {
if( $sourceUnit->matchStartAndRemove( $parts[1] ) ){
$this->targetUnit =& $this->sourceUnit;
} else {
$this->targetUnit = new ConvertUnit( $parts[1] );
}
} elseif( $dp->matchStartAndRemove( $parts[0] ) ) {
$this->decimalPlaces = intval( $parts[1] );
} elseif( $sf->matchStartAndRemove( $parts[0] ) ) {
# It doesn't make any sense to have negative sig-figs
if( intval( $parts[1] ) > 0 ){
$this->significantFigures = intval( $parts[1] );
}
}
} elseif( $linkUnit->matchStartAndRemove( $parts[0] ) ) {
$this->link = true;
} elseif( $abbr->matchStartAndRemove( $parts[0] ) ) {
$this->abbreviate = true;
} elseif( $raw->matchStartAndRemove( $parts[0] ) ) {
$this->raw = true;
} elseif( $parts[0] != '' && !$n++ && !$this->targetUnit instanceof ConvertUnit ){
# First unnamed parameter = output unit
$this->targetUnit = new ConvertUnit( $parts[0] );
}
}
# Get the source unit, if not already set. This throws ConvertError on failure
if ( !$this->sourceUnit instanceof ConvertUnit ){
$this->deduceSourceUnit( $string );
}
# Use the default unit (SI usually)
if( !$this->targetUnit instanceof ConvertUnit ){
$this->targetUnit = $this->sourceUnit->getDefaultUnit();
}
if( $this->targetUnit->dimension->value != $this->sourceUnit->dimension->value ){
throw new ConvertError(
'dimensionmismatch',
$this->sourceUnit->dimension->getName(true),
$this->targetUnit->dimension->getName(true)
);
}
return $this->processString( $string );
}
protected function deduceSourceUnit( $string ){
# Get the unit from the end of the string
$matches = array();
preg_match( self::UNITS_REGEX, $string, $matches );
if( count( $matches ) == 3 ){
$this->sourceUnit = new ConvertUnit( $matches[2] );
} else {
throw new ConvertError( 'nounit' );
}
}
/**
* Identify the values to be converted, and convert them
* @param $string String
*/
protected function processString( $string ){
# Replace values
$string = preg_replace_callback(
self::NUM_REGEX,
array( $this, 'convert' ),
trim( preg_replace( self::UNITS_REGEX, '$1', $string ) )
);
if( $this->raw ){
return $string;
} else {
$unit = $this->targetUnit->getText(
$this->lastValue,
$this->link,
$this->abbreviate
);
return "$string $unit";
}
}
/**
* Express a value in the $sourceUnit in terms of the $targetUnit, preserving
* an appropriate degree of accuracy.
* @param $value String
* @return void
*/
public function convert( $value ){
global $wgContLang;
$valueFloat = floatval( $value[1] );
$newValue = $valueFloat
* $this->sourceUnit->getConversion()
/ $this->targetUnit->getConversion();
if( $this->decimalPlaces !== null && $this->significantFigures !== null ){
# round to the required number of decimal places, or the required number
# of significant figures, whichever is the least precise
$dp = floor( $this->significantFigures - log10( abs( $newValue ) ) ); # Convert SF to DP
$newValue = round( $newValue, max( $dp, $this->decimalPlaces ) );
} elseif( $this->decimalPlaces !== null ){
$newValue = round( $newValue, $this->decimalPlaces );
} elseif( $this->significantFigures !== null ){
$dp = floor( $this->significantFigures - log10( abs( $newValue ) ) ); # Convert SF to DP
$newValue = round( $newValue, $dp );
} else {
# Need to round to a similar accuracy as the original value. To do that we
# select the accuracy which will as closely as possible preserve the maximum
# percentage error in the value. So 36ft = 36 ± 0.5 ft, so the uncertainty
# is ±0.5/36 = ±1.4%. In metres this is 10.9728 ± 1.4%, or 10.9728 ± 0.154
# we take the stance of choosing the limit which is *more* precise than the
# original value.
# Strip sign and exponent
$num = preg_replace( self::NUM_REGEX, '$2', $value[1] );
if( strpos( $num, '.' ) !== false ){
# If there is a decimal point, this is the number of digits after it.
$dpAfter = strlen( $num ) - strpos( $num, '.' ) - 1;
$error = pow( 10, -$dpAfter - 1 ) * 5;
} elseif( $num == 0 ) {
# The logarithms below will be unhappy, and it doesn't actually matter
# what error we come up with, zero is still zero
$error = 1;
} else {
# Number of digits before the point
$dpBefore = floor( log10( abs( $num ) ) );
# Number of digits if we reverse the string = number
# of digits excluding trailing zeros
$dpAfter = floor( log10( abs( strrev( $num ) ) ) );
# How many significant figures to consider numbers like "35000" to have
# is a tricky question. We say 2 here because if people want to ensure
# that the zeros are included, they could write it as 3.500E4
$error = pow( 10, $dpBefore - $dpAfter - 1 ) * 5;
}
$errorFraction = $error / $num;
$i = 10;
while( $i > -10 && ( round( $newValue, $i - 1 ) != 0 ) &&
# Rounding to 10dp avoids floating point errors in exact conversions,
# which are on the order of 1E-16
( round( 5 * pow( 10, -$i ) / round( $newValue, $i - 1 ), 10 ) <= round( $errorFraction, 10 ) ) )
{
$i--;
}
$newValue = round( $newValue, $i );
# We may need to stick significant zeros back onto the number
if( $i > 0 ){
if( strpos( $newValue, '.' ) !== false ){
$newValue = str_pad( $newValue, $i + strpos( $newValue, '.' ) + 1, '0' );
} else {
$newValue .= '.' . str_repeat( '0', $i );
}
}
}
# Store the last value for use in PLURAL later
$this->lastValue = $newValue;
return $this->raw
? $newValue
: $wgContLang->formatNum( $newValue );
}
}
/**
* A dimension
*/
class ConvertDimension {
const MASS = 1; # KILOGRAM
const LENGTH = 10; # METRE
const TIME = 100; # SECOND
const TEMPERATURE = 1E3; # KELVIN
const QUANTITY = 1E4; # MOLE
const CURRENT = 1E5; # AMPERE
const INTENSITY = 1E6; # CANDELA
# fuel efficiencies are ugly and horrible and dimensionally confused, and have the
# same dimensions as LENGTH or 1/LENGTH. But someone wanted to include them... so
# we have up to ten dimensions which can be identified by values of this.
# 0 = sane unit
# 1 = some sort of fuel efficiency
const UGLY_HACK_VALUE = 1E7;
/**
* Dimension constants. These are the values you'd get if you added the SI
* base units together with the weighting given above, also the output from
* getDimensionHash(). Cool thing is, you can add these together to get new
* compound dimensions.
*/
const DIM_DIMENSIONLESS = 0; # Numbers etc
const DIM_LENGTH = 10;
const DIM_AREA = 20;
const DIM_VOLUME = 30;
const DIM_TIME = 100;
const DIM_TIME_SQ = 200;
const DIM_MASS = 1;
const DIM_TEMPERATURE = 1000;
const DIM_SPEED = -90; # LENGTH / TIME
const DIM_ACCELERATION = -190; # LENGTH / TIME_SQ
const DIM_FORCE = -189; # MASS * LENGTH / TIME_SQ
const DIM_TORQUE = -179; # also MASS * AREA / TIME_SQ, but all units are single
const DIM_ENERGY = -179; # MASS * AREA / TIME_SQ, all units are compound
const DIM_PRESSURE = -209; # MASS / ( LENGTH * TIME_SQ )
const DIM_POWER = -79; # MASS * AREA / TIME
const DIM_DENSITY = -29; # MASS / VOLUME
const DIM_FUELEFFICIENCY_PVE = 10000020; # fuel efficiency in VOLUME / LENGTH
const DIM_FUELEFFICIENCY_NVE = 99999990; # fuel efficiency in LENGTH / VOLUME
# Map of dimension names to message keys. This also serves as a list of what
# dimensions will not throw an error when encountered.
public static $legalDimensions = array(
self::DIM_LENGTH => 'length',
self::DIM_AREA => 'area',
self::DIM_VOLUME => 'volume',
self::DIM_TIME => 'time',
self::DIM_TIME_SQ => 'timesquared',
self::DIM_MASS => 'mass',
self::DIM_TEMPERATURE => 'temperature',
self::DIM_SPEED => 'speed',
self::DIM_ACCELERATION => 'acceleration',
self::DIM_FORCE => 'force',
self::DIM_TORQUE => 'torque',
self::DIM_ENERGY => 'energy',
self::DIM_PRESSURE => 'pressure',
self::DIM_POWER => 'power',
self::DIM_DENSITY => 'density',
self::DIM_FUELEFFICIENCY_PVE => 'fuelefficiencypositive',
self::DIM_FUELEFFICIENCY_NVE => 'fuelefficiencynegative',
);
public $value;
protected $name;
public function __construct( $var, $var2=null ){
static $legalDimensionsFlip;
if( is_string( $var ) ){
if( $legalDimensionsFlip === null ){
$legalDimensionsFlip = array_flip( self::$legalDimensions );
}
if( isset( $legalDimensionsFlip[$var] ) ){
$dim = $legalDimensionsFlip[$var];
} else {
# Should be unreachable
throw new ConvertError( 'unknowndimension' );
}
} elseif( $var instanceof self ){
$dim = $var->value;
} else {
$dim = intval( $var );
}
if( $var2 === null ){
$this->value = $dim;
$this->name = $this->compoundName = self::$legalDimensions[$this->value];
} else {
if( is_string( $var2 ) ){
if( $legalDimensionsFlip === null ){
$legalDimensionsFlip = array_flip( self::$legalDimensions );
}
if( isset( $legalDimensionsFlip[$var2] ) ){
$dim2 = $legalDimensionsFlip[$var2];
} else {
# Should be unreachable
throw new ConvertError( 'unknowndimension' );
}
} elseif( $var2 instanceof self ){
$dim2 = $var2->value;
} else {
$dim2 = intval( $var2 );
}
$this->value = $dim - $dim2;
if( in_array( $this->value, array_keys( self::$legalDimensions ) ) ){
$this->name = self::$legalDimensions[$this->value];
$this->compoundName = array(
self::$legalDimensions[$var],
self::$legalDimensions[$var2],
);
} else {
# Some combinations of units are fine (carats per bushel is a perfectly good,
# if somewhat bizarre, measure of density, for instance). But others (like
# carats per miles-per-gallon) are definitely not.
# TODO: this allows compound units like <gigawatthours>/<pascal> as a unit
# of volume; is that a good thing or a bad thing?
throw new ConvertError( 'invalidcompoundunit', "$var/$var2" );
}
}
}
/**
* Convert to string. Magic in PHP 5.1 and above.
* @return String
*/
public function __toString(){
return strval( $this->name );
}
/**
* Get the name, or names, of the dimension
* @return String|Array of String
*/
public function getName( $expandCompound = false ){
return $expandCompound
? $this->name
: $this->compoundName;
}
/**
* Get the localised name of the dimension. Output is unescaped
* @return String
*/
public function getLocalisedName(){
return wfMsg( "pfunc-convert-dimension-{$this->name}" );
}
}
class ConvertUnit {
/**
* array(
* DIMENSION => array(
* UNIT => array(
* CONVERSION,
* REGEX
* )
* )
* )
*/
protected static $units = array(
ConvertDimension::DIM_LENGTH => array(
'gigametre' => array( 1000000000, 'Gm' ),
'megametre' => array( 1000000, '(?:(?-i)Mm)' ), # Case-sensitivity is forced
'kilometre' => array( 1000, 'km' ),
'hectometre' => array( 100, 'hm' ),
'decametre' => array( 10, 'dam' ),
'metre' => array( 1, 'm' ),
'decimetre' => array( 0.1, 'dm' ),
'centimetre' => array( 0.01, 'cm' ),
'millimetre' => array( 0.001, '(?:(?-i)mm)' ), # Case-sensitivity is forced
'micrometre' => array( 0.0001, '\x03BCm|\x00B5m|um' ), # There are two similar mu characters
'nanometre' => array( 0.0000001, 'nm' ),
'angstrom' => array( 0.00000001, '\x00C5' ),
'mile' => array( 1609.344, 'mi|miles?' ),
'furlong' => array( 201.168, 'furlong' ),
'chain' => array( 20.1168 , 'chain' ),
'rod' => array( 5.0292, 'rod|pole|perch' ),
'fathom' => array( 1.8288, 'fathom' ),
'yard' => array( 0.9144, 'yards?|yd' ),
'foot' => array( 0.3048, 'foot|feet|ft' ),
'hand' => array( 0.1016, 'hands?' ),
'inch' => array( 0.0254, 'inch|inches|in' ),
'nauticalmile' => array( 1852, 'nauticalmiles?|nmi' ),
'nauticalmileuk' => array( 1853.184, 'oldUKnmi|Brnmi|admi' ),
'nauticalmileus' => array( 1853.24496, 'oldUSnmi' ),
'gigaparsec' => array( 3.0856775813057E25, 'gigaparsecs?|Gpc' ),
'megaparsec' => array( 3.0856775813057E22, 'megaparsecs?|Mpc' ),
'kiloparsec' => array( 3.0856775813057E19, 'kiloparsecs?|kpc' ),
'parsec' => array( 3.0856775813057E16, 'parsecs?|pc' ),
'gigalightyear' => array( 9.4607304725808E24, 'gigalightyears?|Gly' ),
'mrgalightyear' => array( 9.4607304725808E21, 'megalightyears?|Mly' ),
'kilolightyear' => array( 9.4607304725808E18, 'kilolightyears?|kly' ),
'lightyear' => array( 9.4607304725808E15, 'lightyears?|ly' ),
'astronomicalunit' => array( 149597870700, 'astronomicalunits?|AU' ),
),
ConvertDimension::DIM_AREA => array(
'squarekilometre' => array( 1E6, 'km2|km\^2' ),
'squaremetre' => array( 1, 'm2|m\^2' ),
'squarecentimetre' => array( 1E-4, 'cm2|cm\^2' ),
'squaremillimetre' => array( 1E-6, 'mm2|mm\^2' ),
'hectare' => array( 1E4, 'hectares?|ha' ),
'squaremile' => array( 2589988.110336, 'sqmi|mi2|mi\^2' ),
'acre' => array( 4046.856422 , 'acres?' ),
'squareyard' => array( 0.83612736, 'sqyd|yd2|yd\^2' ),
'squarefoot' => array( 0.09290304, 'sqft|ft2|ft\^2' ),
'squareinch' => array( 0.00064516, 'sqin|in2|in\^2' ),
'squarenauticalmile' => array( 3429904, 'sqnmi|nmi2|nmi\^2' ),
'dunam' => array( 1000, 'dunam' ),
'tsubo' => array( 3.305785, 'tsubo' ),
),
ConvertDimension::DIM_VOLUME => array(
'cubicmetre' => array( 1, 'm3|m\^3' ),
'cubiccentimetre' => array( 1E-6, 'cm3|cm\^3' ),
'cubicmillimetre' => array( 1E-9, 'mm3|mm\^3' ),
'kilolitre' => array( 1, 'kl' ),
'litre' => array( 1E-3 , 'l' ),
'centilitre' => array( 1E-5, 'cl' ),
'millilitre' => array( 1E-6, 'ml' ),
'cubicyard' => array( 0.764554857984, 'cuyd|yd3|yd\^3' ),
'cubicfoot' => array( 0.028316846592, 'cuft|ft3|ft\^3' ),
'cubicinch' => array( 0.000016387064, 'cuin|in3|in\^3' ),
'barrel' => array( 0.16365924, 'bbl|barrels?|impbbl' ),
'bushel' => array( 0.03636872, 'bsh|bushels?|impbsh' ),
'gallon' => array( 0.00454609, 'gal|gallons?|impgal' ),
'quart' => array( 0.0011365225, 'qt|quarts?|impqt' ),
'pint' => array( 0.00056826125, 'pt|pints?|imppt' ),
'fluidounce' => array( 0.0000284130625, 'floz|impfloz' ),
'barrelus' => array( 0.119240471196, 'usbbl' ),
'barreloil' => array( 0.158987294928, 'oilbbl' ),
'barrelbeer' => array( 0.117347765304, 'beerbbl' ),
'usgallon' => array( 0.003785411784, 'usgal' ),
'usquart' => array( 0.000946352946, 'usqt' ),
'uspint' => array( 0.000473176473, 'uspt' ),
'usfluidounce' => array( 0.0000295735295625, 'usfloz' ),
'usdrybarrel' => array( 0.11562819898508, 'usdrybbl' ),
'usbushel' => array( 0.03523907016688, 'usbsh' ),
'usdrygallon' => array( 0.00440488377086, 'usdrygal' ),
'usdryquart' => array( 0.001101220942715, 'usdryqt' ),
'usdrypint' => array( 0.0005506104713575, 'usdrypt' ),
),
ConvertDimension::DIM_TIME => array(
'year' => array( 31557600, 'yr' ),
'day' => array( 86400, 'days?' ),
'hour' => array( 3600, 'hours?|hr|h' ),
'minute' => array( 60, 'minutes?|mins?' ),
'second' => array( 1, 's' ),
),
ConvertDimension::DIM_SPEED => array(
'knot' => array( 0.514444444, 'knot|kn' ),
'speedoflight' => array( 2.9979E8, 'c' ),
),
ConvertDimension::DIM_PRESSURE => array(
'gigapascal' => array( 1000000000, 'GPa' ),
'megapascal' => array( 1000000, '(?:(?-i)M[Pp]a)' ), # Case-sensitivity is forced
'kilopascal' => array( 1000, 'kPa' ),
'hectopascal' => array( 100, 'hPa' ),
'pascal' => array( 1, 'Pa' ),
'millipascal' => array( 0.001, '(?:(?-i)m[Pp]a)' ), # Case-sensitivity is forced
'bar' => array( 100000, 'bar' ),
'decibar' => array( 10000, 'dbar' ),
'milibar' => array( 100 , 'mbar|mb' ),
'kilobarye' => array( 100, 'kba' ),
'barye' => array( 0.1, 'ba' ),
'atmosphere' => array( 101325, 'atm|atmospheres?' ),
'torr' => array( 133.32237, 'torr' ),
'mmhg' => array( 133.322387415, 'mmHg' ),
'inhg' => array( 3386.38864034, 'inHg' ),
'psi' => array( 6894.757293, 'psi' ),
),
# TODO: other dimensions as needed
);
# Default units for each dimension
# TODO: this should ideally be localisable
protected static $defaultUnit = array(
ConvertDimension::DIM_LENGTH => 'metre',
ConvertDimension::DIM_AREA => 'squaremetre',
ConvertDimension::DIM_VOLUME => 'cubicmetre',
ConvertDimension::DIM_TIME => 'second',
ConvertDimension::DIM_SPEED => 'metre/second',
ConvertDimension::DIM_PRESSURE => 'pascal',
);
# An array of preprocessing conversions to apply to units
protected static $unitConversions = array(
'/^mph$/ui' => 'mi/h',
);
# Map of UNIT => DIMENSION, created on construct
protected static $dimensionMap = false;
/***************** MEMBER VARIABLES *****************/
# @var ConvertDimension
public $dimension;
# What number you need to multiply this unit by to get the equivalent
# value in SI base units
protected $conversion = 1;
# A regex which matches the unit
protected $regex;
# The name of the unit (key into $units[$dimension] above
protected $unitName;
/***************** MEMBER FUNCTIONS *****************/
/**
* Constructor
* @param $rawUnit String
*/
public function __construct( $rawUnit ){
if( self::$dimensionMap === false ){
self::$dimensionMap = array();
foreach( self::$units as $dimension => $arr ){
foreach( $arr as $unit => $val ){
self::$dimensionMap[$unit] = $dimension;
}
}
}
$this->parseUnit( $rawUnit );
}
protected function parseUnit( $rawUnit ){
# Do mappings like 'mph' --> 'mi/h'
$rawUnit = preg_replace(
array_keys( self::$unitConversions ),
array_values( self::$unitConversions ),
$rawUnit
);
$parts = explode( '/', $rawUnit );
array_map( 'trim', $parts );
if( count( $parts ) == 1 ){
# Single unit
foreach( self::$units as $dimension => $units ){
foreach( $units as $unit => $data ){
if( $rawUnit == $unit || preg_match( "/^({$data[1]})$/ui", $parts[0] ) ){
$this->dimension = new ConvertDimension( self::$dimensionMap[$unit] );
$this->conversion = self::$units[$this->dimension->value][$unit][0];
$this->regex = $data[1];
$this->unitName = $unit;
return;
}
}
}
# Unknown unit
throw new ConvertError( 'unknownunit', $rawUnit );
} elseif( count( $parts ) == 2 ){
# Compound unit.
$top = new self( $parts[0] );
$bottom = new self( $parts[1] );
$this->dimension = new ConvertDimension( $top->dimension, $bottom->dimension );
$this->conversion = $top->conversion / $bottom->conversion;
$this->regex = "(?:{$top->regex})/(?:{$bottom->regex})";
$this->unitName = array( $top->unitName, $bottom->unitName );
return;
} else {
# Whaaat? Too many parts
throw new ConvertError( 'doublecompoundunit', $rawUnit );
}
}
public function getConversion(){
return $this->conversion;
}
public function getRegex(){
return $this->regex;
}
/**
* Get the text of the unit
* @param $value String number for PLURAL support
* @param $link Bool
* @return String
*/
public function getText( $value, $link=false, $abbreviate=false ){
global $wgContLang;
$value = $wgContLang->formatNum( $value );
$abbr = $abbreviate ? '-abbr' : '';
if( !is_array( $this->unitName ) ){
$msg = "pfunc-convert-unit-{$this->dimension->getName()}-{$this->unitName}";
$msgText = wfMsgExt( "$msg$abbr", array( 'parsemag', 'content' ), $value );
if( $link && !wfEmptyMsg( "$msg-link" ) ){
$title = Title::newFromText( wfMsgForContentNoTrans( "$msg-link" ) );
$msgText = "[[{$title->getFullText()}|$msgText]]";
}
} elseif( !wfEmptyMsg( "pfunc-convert-unit-{$this->dimension->getName(true)}-{$this->unitName[0]}-{$this->unitName[1]}" ) ){
# A wiki has created, say, [[MediaWiki:pfunc-convert-unit-speed-metres-second]]
# so they can have it display "<metres per second>" rather than
# "<metres>/<second>"
$msg = "pfunc-convert-unit-{$this->dimension->getName(true)}-{$this->unitName[0]}-{$this->unitName[1]}";
$msgText = wfMsgExt( "$msg$abbr", array( 'parsemag', 'content' ), $value );
if( $link && !wfEmptyMsg( "$msg-link" ) ){
$title = Title::newFromText( wfMsgForContentNoTrans( "$msg-link" ) );
if( $title instanceof Title ){
$msgText = "[[$title|$msgText]]";
}
}
} else {
$dimensionNames = $this->dimension->getName();
$msg = "pfunc-convert-unit-{$dimensionNames[0]}-{$this->unitName[0]}";
$msgText = wfMsgExt( "$msg$abbr", array( 'parsemag', 'content' ), $value );
if( $link && !wfEmptyMsg( "$msg-link" ) ){
$title = Title::newFromText( wfMsgForContentNoTrans( "$msg-link" ) );
$msgText = "[[{$title->getFullText()}|$msgText]]";
}
$msg2 = "pfunc-convert-unit-{$dimensionNames[1]}-{$this->unitName[1]}";
$msg2Text = wfMsgExt( "$msg2$abbr", array( 'parsemag', 'content' ), 1 ); # Singular for denominator
if( $link && !wfEmptyMsg( "$msg2-link" ) ){
$title = Title::newFromText( wfMsgForContentNoTrans( "$msg2-link" ) );
if( $title instanceof Title ){
$msg2Text = "[[{$title->getFullText()}|$msg2Text]]";
}
}
$msgText = "$msgText/$msg2Text";
}
return $msgText;
}
/**
* Get the default (usually SI) unit associated with this particular dimension
* @return ConvertUnit
*/
public function getDefaultUnit(){
return new ConvertUnit( self::$defaultUnit[$this->dimension->value] );
}
}

View file

@ -22,6 +22,13 @@ $magicWords['en'] = array(
'timel' => array( 0, 'timel' ),
'rel2abs' => array( 0, 'rel2abs' ),
'titleparts' => array( 0, 'titleparts' ),
'convert' => array( 0, 'convert' ),
'sourceunit' => array( 0, '#sourceunit' ),
'targetunit' => array( 0, '#targetunit' ),
'linkunit' => array( 0, '#linkunit' ),
'decimalplaces' => array( 0, '#dp' ),
'significantfigures' => array( 0, '#sf' ),
'abbreviate' => array( 0, '#abbreviate' ),
'len' => array( 0, 'len' ),
'pos' => array( 0, 'pos' ),
'rpos' => array( 0, 'rpos' ),

View file

@ -27,7 +27,244 @@ $messages['en'] = array(
'pfunc_expr_invalid_argument_ln' => 'Invalid argument for ln: <= 0',
'pfunc_expr_unknown_error' => 'Expression error: Unknown error ($1)',
'pfunc_expr_not_a_number' => 'In $1: result is not a number',
'pfunc_string_too_long' => 'Error: String exceeds $1 character limit',
'pfunc_string_too_long' => 'Error: string exceeds $1 character limit',
'pfunc-convert-dimensionmismatch' => 'Error: cannot convert between units of "$1" and "$2"',
'pfunc-convert-unknownunit' => 'Error: unknown unit "$1"',
'pfunc-convert-unknowndimension' => 'Error: unknown dimension "$1"',
'pfunc-convert-invalidcompoundunit' => 'Error: invalid compound unit "$1"',
'pfunc-convert-nounit' => 'Error: no source unit given',
'pfunc-convert-doublecompoundunit' => 'Error: cannot parse double compound units like "$1"',
# DIMENSION NAMES
'pfunc-convert-dimension-length' => 'length',
'pfunc-convert-dimension-area' => 'area',
'pfunc-convert-dimension-volume' => 'volume',
'pfunc-convert-dimension-time' => 'time',
'pfunc-convert-dimension-timesquared' => 'time<sup>2</sup>',
'pfunc-convert-dimension-mass' => 'mass',
'pfunc-convert-dimension-speed' => 'speed',
'pfunc-convert-dimension-temperature' => 'temperature',
'pfunc-convert-dimension-acceleration' => 'acceleration',
'pfunc-convert-dimension-force' => 'force',
'pfunc-convert-dimension-torque' => 'torque',
'pfunc-convert-dimension-energy' => 'energy',
'pfunc-convert-dimension-power' => 'power',
'pfunc-convert-dimension-pressure' => 'pressure',
'pfunc-convert-dimension-density' => 'density',
'pfunc-convert-dimension-fuelefficiencypositive' => 'fuelefficiencypositive',
'pfunc-convert-dimension-fuelefficiencynegative' => 'fuelefficiencynegative',
# LENGTH
'pfunc-convert-unit-length-gigametre' => '{{PLURAL:$1|gigametre|gigametres}}',
'pfunc-convert-unit-length-megametre' => '{{PLURAL:$1|megametre|megametres}}',
'pfunc-convert-unit-length-kilometre' => '{{PLURAL:$1|kilometre|kilometres}}',
'pfunc-convert-unit-length-hectometre' => '{{PLURAL:$1|hectometre|hectometres}}',
'pfunc-convert-unit-length-decametre' => '{{PLURAL:$1|decametre|decametres}}',
'pfunc-convert-unit-length-metre' => '{{PLURAL:$1|metre|metres}}',
'pfunc-convert-unit-length-decimetre' => '{{PLURAL:$1|decimetre|decimetres}}',
'pfunc-convert-unit-length-centimetre' => '{{PLURAL:$1|centimetre|centimetres}}',
'pfunc-convert-unit-length-millimetre' => '{{PLURAL:$1|millimetre|millimetres}}',
'pfunc-convert-unit-length-micrometre' => '{{PLURAL:$1|micrometre|micrometres}}',
'pfunc-convert-unit-length-nanometre' => '{{PLURAL:$1|nanometre|nanometres}}',
'pfunc-convert-unit-length-angstrom' => '{{PLURAL:$1|angstrom|angstroms}}',
'pfunc-convert-unit-length-mile' => '{{PLURAL:$1|mile|miles}}',
'pfunc-convert-unit-length-furlong' => '{{PLURAL:$1|furlong|furlongs}}',
'pfunc-convert-unit-length-chain' => '{{PLURAL:$1|chain|chains}}',
'pfunc-convert-unit-length-rod' => '{{PLURAL:$1|rod|rods}}',
'pfunc-convert-unit-length-fathom' => '{{PLURAL:$1|fathom|fathoms}}',
'pfunc-convert-unit-length-yard' => '{{PLURAL:$1|yard|yards}}',
'pfunc-convert-unit-length-foot' => '{{PLURAL:$1|foot|feet}}',
'pfunc-convert-unit-length-hand' => '{{PLURAL:$1|hand|hands}}',
'pfunc-convert-unit-length-inch' => '{{PLURAL:$1|inch|inches}}',
'pfunc-convert-unit-length-nauticalmile' => '{{PLURAL:$1|nautical mile|nautical miles}}',
'pfunc-convert-unit-length-nauticalmileuk' => '{{PLURAL:$1|nautical mile (pre-1970 British)|nautical miles (pre-1970 British)}}',
'pfunc-convert-unit-length-nauticalmileus' => '{{PLURAL:$1|nautical mile (pre-1954 US)|nautical miles (pre-1954 US)}}',
'pfunc-convert-unit-length-gigaparsec' => '{{PLURAL:$1|gigaparsec|gigaparsecs}}',
'pfunc-convert-unit-length-megaparsec' => '{{PLURAL:$1|megaparsec|megaparsecs}}',
'pfunc-convert-unit-length-kiloparsec' => '{{PLURAL:$1|kiloparsec|kiloparsecs}}',
'pfunc-convert-unit-length-parsec' => '{{PLURAL:$1|parsec|parsecs}}',
'pfunc-convert-unit-length-gigalightyear' => '{{PLURAL:$1|gigalightyear|gigalightyears}}',
'pfunc-convert-unit-length-mrgalightyear' => '{{PLURAL:$1|megalightyear|megalightyears}}',
'pfunc-convert-unit-length-kilolightyear' => '{{PLURAL:$1|kilolightyear|kilolightyears}}',
'pfunc-convert-unit-length-lightyear' => '{{PLURAL:$1|lightyear|lightyears}}',
'pfunc-convert-unit-length-astronomicalunit' => '{{PLURAL:$1|astronomical unit|astronomical units}}',
'pfunc-convert-unit-length-gigametre-abbr' => 'Gm',
'pfunc-convert-unit-length-megametre-abbr' => 'Mm',
'pfunc-convert-unit-length-kilometre-abbr' => 'km',
'pfunc-convert-unit-length-hectometre-abbr' => 'hm',
'pfunc-convert-unit-length-decametre-abbr' => 'dam',
'pfunc-convert-unit-length-metre-abbr' => 'm',
'pfunc-convert-unit-length-decimetre-abbr' => 'dm',
'pfunc-convert-unit-length-centimetre-abbr' => 'cm',
'pfunc-convert-unit-length-milimetre-abbr' => 'mm',
'pfunc-convert-unit-length-micrometre-abbr' => 'μm',
'pfunc-convert-unit-length-nanometre-abbr' => 'nm',
'pfunc-convert-unit-length-angstrom-abbr' => 'Å',
'pfunc-convert-unit-length-mile-abbr' => 'mi',
'pfunc-convert-unit-length-furlong-abbr' => 'furlong',
'pfunc-convert-unit-length-chain-abbr' => 'chain',
'pfunc-convert-unit-length-rod-abbr' => 'rd',
'pfunc-convert-unit-length-fathom-abbr' => 'fathom',
'pfunc-convert-unit-length-yard-abbr' => 'yd',
'pfunc-convert-unit-length-foot-abbr' => 'ft',
'pfunc-convert-unit-length-hand-abbr' => 'h',
'pfunc-convert-unit-length-inch-abbr' => 'in',
'pfunc-convert-unit-length-nauticalmile-abbr' => 'nmi',
'pfunc-convert-unit-length-nauticalmileuk-abbr' => 'nmi (Brit)',
'pfunc-convert-unit-length-nauticalmileus-abbr' => 'nmi (pre-1954 US)',
'pfunc-convert-unit-length-gigaparsec-abbr' => 'Gpc',
'pfunc-convert-unit-length-megaparsec-abbr' => 'Mpc',
'pfunc-convert-unit-length-kiloparsec-abbr' => 'kpc',
'pfunc-convert-unit-length-parsec-abbr' => 'pc',
'pfunc-convert-unit-length-gigalightyear-abbr' => 'Gly',
'pfunc-convert-unit-length-mrgalightyear-abbr' => 'Mly',
'pfunc-convert-unit-length-kilolightyear-abbr' => 'kly',
'pfunc-convert-unit-length-lightyear-abbr' => 'ly',
'pfunc-convert-unit-length-astronomicalunit-abbr' => 'AU',
# AREA #
'pfunc-convert-unit-area-squarekilometre' => '{{PLURAL:$1|square kilometre|square kilometres}}',
'pfunc-convert-unit-area-squaremetre' => '{{PLURAL:$1|square metre|square metres}}',
'pfunc-convert-unit-area-squarecentimetre' => '{{PLURAL:$1|square centimetre|square centimetres}}',
'pfunc-convert-unit-area-squaremillimetre' => '{{PLURAL:$1|square millimetre|square millimetres}}',
'pfunc-convert-unit-area-hectare' => '{{PLURAL:$1|hectare|hectares}}',
'pfunc-convert-unit-area-squaremile' => '{{PLURAL:$1|square mile|square miles}}',
'pfunc-convert-unit-area-acre' => '{{PLURAL:$1|acre|acres}}',
'pfunc-convert-unit-area-squareyard' => '{{PLURAL:$1|square yard|square yards}}',
'pfunc-convert-unit-area-squarefoot' => '{{PLURAL:$1|square foot|square feet}}',
'pfunc-convert-unit-area-squareinch' => '{{PLURAL:$1|square inch|square inches}}',
'pfunc-convert-unit-area-squarenauticalmile' => '{{PLURAL:$1|square nautical mile|square nautical miles}}',
'pfunc-convert-unit-area-dunam' => '{{PLURAL:$1|dunam|dunams}}',
'pfunc-convert-unit-area-tsubo' => '{{PLURAL:$1|tsubo|tsubo}}',
'pfunc-convert-unit-area-squarekilometre-abbr' => 'km<sup>2</sup>',
'pfunc-convert-unit-area-squaremetre-abbr' => 'm<sup>2</sup>',
'pfunc-convert-unit-area-squarecentimetre-abbr' => 'cm<sup>2</sup>',
'pfunc-convert-unit-area-squaremillimetre-abbr' => 'mm<sup>2</sup>',
'pfunc-convert-unit-area-hectare-abbr' => 'ha',
'pfunc-convert-unit-area-squaremile-abbr' => 'sq mi',
'pfunc-convert-unit-area-acre-abbr' => 'acre',
'pfunc-convert-unit-area-squareyard-abbr' => 'sq yd',
'pfunc-convert-unit-area-squarefoot-abbr' => 'sq ft',
'pfunc-convert-unit-area-squareinch-abbr' => 'sq in',
'pfunc-convert-unit-area-squarenauticalmile-abbr' => 'sq nmi',
'pfunc-convert-unit-area-dunam-abbr' => 'dunam',
'pfunc-convert-unit-area-tsubo-abbr' => 'tsubo',
# TIME #
'pfunc-convert-unit-time-second' => '{{PLURAL:$1|second|seconds}}',
'pfunc-convert-unit-time-year' => '{{PLURAL:$1|year|years}}',
'pfunc-convert-unit-time-day' => '{{PLURAL:$1|day|days}}',
'pfunc-convert-unit-time-hour' => '{{PLURAL:$1|hour|hours}}',
'pfunc-convert-unit-time-minute' => '{{PLURAL:$1|minute|minutes}}',
'pfunc-convert-unit-time-second-abbr' => 's',
'pfunc-convert-unit-time-year-abbr' => 'yr',
'pfunc-convert-unit-time-day-abbr' => 'day',
'pfunc-convert-unit-time-hour-abbr' => 'hr',
'pfunc-convert-unit-time-minute-abbr' => 'min',
# VOLUME #
'pfunc-convert-unit-volume-cubicmetre' => '{{PLURAL:$1|cubic metre|cubic metres}}',
'pfunc-convert-unit-volume-cubiccentimetre' => '{{PLURAL:$1|cubic centimetre|cubic centimetres}}',
'pfunc-convert-unit-volume-cubicmillimetre' => '{{PLURAL:$1|cubic millimetre|cubic millimetres}}',
'pfunc-convert-unit-volume-kilolitre' => '{{PLURAL:$1|kilolitre|kilolitres}}',
'pfunc-convert-unit-volume-litre' => '{{PLURAL:$1|litre|litres}}',
'pfunc-convert-unit-volume-centilitre' => '{{PLURAL:$1|centilitre|centilitres}}',
'pfunc-convert-unit-volume-millilitre' => '{{PLURAL:$1|millilitre|millilitres}}',
'pfunc-convert-unit-volume-cubicyard' => '{{PLURAL:$1|cubic yard|cubic yards}}',
'pfunc-convert-unit-volume-cubicfoot' => '{{PLURAL:$1|cubic foot|cubic feet}}',
'pfunc-convert-unit-volume-cubicinch' => '{{PLURAL:$1|cubic inch|cubic inches}}',
'pfunc-convert-unit-volume-barrel' => '{{PLURAL:$1|barrel|barrels}}',
'pfunc-convert-unit-volume-bushel' => '{{PLURAL:$1|bushel|bushels}}',
'pfunc-convert-unit-volume-gallon' => '{{PLURAL:$1|gallon|gallons}}',
'pfunc-convert-unit-volume-quart' => '{{PLURAL:$1|quart|quarts}}',
'pfunc-convert-unit-volume-pint' => '{{PLURAL:$1|pint|pints}}',
'pfunc-convert-unit-volume-fluidounce' => '{{PLURAL:$1|fluid ounce|fluid ounces}}',
'pfunc-convert-unit-volume-barrelus' => '{{PLURAL:$1|US barrel|US barrels}}',
'pfunc-convert-unit-volume-barreloil' => '{{PLURAL:$1|barrel|barrel}}',
'pfunc-convert-unit-volume-barrelbeer' => '{{PLURAL:$1|barrel|barrel}}',
'pfunc-convert-unit-volume-usgallon' => '{{PLURAL:$1|US gallon|US gallons}}',
'pfunc-convert-unit-volume-usquart' => '{{PLURAL:$1|US quart|US quarts}}',
'pfunc-convert-unit-volume-uspint' => '{{PLURAL:$1|US pint|US pints}}',
'pfunc-convert-unit-volume-usfluidounce' => '{{PLURAL:$1|US fluid ounce|US fluid ounces}}',
'pfunc-convert-unit-volume-usdrybarrel' => '{{PLURAL:$1|US dry barrel|US dry barrels}}',
'pfunc-convert-unit-volume-usbushel' => '{{PLURAL:$1|US bushel|US bushels}}',
'pfunc-convert-unit-volume-usdrygallon' => '{{PLURAL:$1|US dry gallon|US dry gallons}}',
'pfunc-convert-unit-volume-usdryquart' => '{{PLURAL:$1|US dry quart|US dry quarts}}',
'pfunc-convert-unit-volume-usdrypint' => '{{PLURAL:$1|US dry pint|US dry pints}}',
'pfunc-convert-unit-volume-cubicmetre-abbr' => 'm<sup>3</sup>',
'pfunc-convert-unit-volume-cubiccentimetre-abbr' => 'cm<sup>3</sup>',
'pfunc-convert-unit-volume-cubicmillimetre-abbr' => 'mm<sup>3</sup>',
'pfunc-convert-unit-volume-kilolitre-abbr' => 'kl',
'pfunc-convert-unit-volume-litre-abbr' => 'l',
'pfunc-convert-unit-volume-centilitre-abbr' => 'cl',
'pfunc-convert-unit-volume-millilitre-abbr' => 'ml',
'pfunc-convert-unit-volume-cubicyard-abbr' => 'cu yd',
'pfunc-convert-unit-volume-cubicfoot-abbr' => 'cu ft',
'pfunc-convert-unit-volume-cubicinch-abbr' => 'cu in',
'pfunc-convert-unit-volume-barrel-abbr' => 'bbl',
'pfunc-convert-unit-volume-bushel-abbr' => 'bsh',
'pfunc-convert-unit-volume-gallon-abbr' => 'gal',
'pfunc-convert-unit-volume-quart-abbr' => 'qt',
'pfunc-convert-unit-volume-pint-abbr' => 'pt',
'pfunc-convert-unit-volume-fluidounce-abbr' => 'fl oz',
'pfunc-convert-unit-volume-barrelus-abbr' => 'US bbl',
'pfunc-convert-unit-volume-barreloil-abbr' => 'bbl',
'pfunc-convert-unit-volume-barrelbeer-abbr' => 'bbl',
'pfunc-convert-unit-volume-usgallon-abbr' => 'US gal',
'pfunc-convert-unit-volume-usquart-abbr' => 'US qt',
'pfunc-convert-unit-volume-uspint-abbr' => 'US pt',
'pfunc-convert-unit-volume-usfluidounce-abbr' => 'US fl oz',
'pfunc-convert-unit-volume-usdrybarrel-abbr' => 'US bbl',
'pfunc-convert-unit-volume-usbushel-abbr' => 'US bsh',
'pfunc-convert-unit-volume-usdrygallon-abbr' => 'US dry gal',
'pfunc-convert-unit-volume-usdryquart-abbr' => 'US dry qt',
'pfunc-convert-unit-volume-usdrypint-abbr' => 'US dry pt',
# SPEED
'pfunc-convert-unit-speed-mile-hour' => 'miles per hour',
'pfunc-convert-unit-speed-speedoflight' => 'c',
'pfunc-convert-unit-speed-mile-hour-abbr' => 'mph',
'pfunc-convert-unit-speed-speedoflight-abbr' => 'c',
# PRESSURE
'pfunc-convert-unit-pressure-gigapascal' => '{{PLURAL:$1|gigapascal|gigapascals}}',
'pfunc-convert-unit-pressure-megapascal' => '{{PLURAL:$1|megapascal|megapascals}}',
'pfunc-convert-unit-pressure-kilopascal' => '{{PLURAL:$1|kilopascal|kilopascals}}',
'pfunc-convert-unit-pressure-hectopascal' => '{{PLURAL:$1|hectopascal|hectopascals}}',
'pfunc-convert-unit-pressure-pascal' => '{{PLURAL:$1|pascal|pascals}}',
'pfunc-convert-unit-pressure-millipascal' => '{{PLURAL:$1|millipascal|millipascals}}',
'pfunc-convert-unit-pressure-bar' => 'bar',
'pfunc-convert-unit-pressure-decibar' => 'decibar',
'pfunc-convert-unit-pressure-millibar' => 'millibar',
'pfunc-convert-unit-pressure-kilobarye' => 'kilobarye',
'pfunc-convert-unit-pressure-barye' => 'barye',
'pfunc-convert-unit-pressure-atmosphere' => '{{PLURAL:$1|atmosphere|atmospheres}}',
'pfunc-convert-unit-pressure-torr' => '{{PLURAL:$1|Torr|Torr}}',
'pfunc-convert-unit-pressure-mmhg' => '{{PLURAL:$1|milimetre of mercury|milimetres of mercury}}',
'pfunc-convert-unit-pressure-inhg' => '{{PLURAL:$1|inch of mercury|inches of mercury}}',
'pfunc-convert-unit-pressure-psi' => '{{PLURAL:$1|pound per square-inch|pounds per square-inch}}',
'pfunc-convert-unit-pressure-gigapascal-abbr' => 'GPa',
'pfunc-convert-unit-pressure-megapascal-abbr' => 'MPa',
'pfunc-convert-unit-pressure-kilopascal-abbr' => 'kPa',
'pfunc-convert-unit-pressure-hectopascal-abbr' => 'hPa',
'pfunc-convert-unit-pressure-pascal-abbr' => 'Pa',
'pfunc-convert-unit-pressure-millipascal-abbr' => 'mPa',
'pfunc-convert-unit-pressure-bar-abbr' => 'bar',
'pfunc-convert-unit-pressure-decibar-abbr' => 'dbar',
'pfunc-convert-unit-pressure-milibar-abbr' => 'mbar',
'pfunc-convert-unit-pressure-kilobarye-abbr' => 'kBa',
'pfunc-convert-unit-pressure-barye-abbr' => 'Ba',
'pfunc-convert-unit-pressure-atmosphere-abbr' => 'atm',
'pfunc-convert-unit-pressure-torr-abbr' => 'Torr',
'pfunc-convert-unit-pressure-mmhg-abbr' => 'mmHg',
'pfunc-convert-unit-pressure-inhg-abbr' => 'inHg',
'pfunc-convert-unit-pressure-psi-abbr' => 'psi',
);
/** Message documentation (Message documentation)

View file

@ -46,6 +46,7 @@ $wgExtensionMessagesFiles['ParserFunctionsMagic'] = dirname( __FILE__ ) . '/Pars
$wgParserTestFiles[] = dirname( __FILE__ ) . "/funcsParserTests.txt";
$wgParserTestFiles[] = dirname( __FILE__ ) . "/stringFunctionTests.txt";
$wgParserTestFiles[] = dirname( __FILE__ ) . "/convertTests.txt";
function wfSetupParserFunctions() {
global $wgPFHookStub, $wgHooks;
@ -89,6 +90,7 @@ class ParserFunctions_HookStub {
$parser->setFunctionHook( 'timel', array( &$this, 'localTime' ) );
$parser->setFunctionHook( 'rel2abs', array( &$this, 'rel2abs' ) );
$parser->setFunctionHook( 'titleparts', array( &$this, 'titleparts' ) );
$parser->setFunctionHook( 'convert', array( &$this, 'convert' ) );
// String Functions
if ( $wgPFEnableStringFunctions ) {

View file

@ -512,6 +512,29 @@ class ExtParserFunctions {
}
}
/**
* Get a ConvertParser object
* @return ConvertParser
*/
protected function &getConvertParser() {
if ( !isset( $this->mConvertParser ) ) {
if ( !class_exists( 'ConvertParser' ) ) {
require( dirname( __FILE__ ) . '/Convert.php' );
}
$this->mConvertParser = new ConvertParser;
}
return $this->mConvertParser;
}
public function convert( /*...*/ ) {
try {
$args = func_get_args();
return $this->getConvertParser()->execute( $args );
} catch ( ConvertError $e ) {
return $e->getMessage();
}
}
// Verifies parameter is less than max string length.
private function checkLength( $text ) {
global $wgPFStringLengthLimit;

193
convertTests.txt Normal file
View file

@ -0,0 +1,193 @@
!! test
Simple conversion
!! input
{{#convert: 10 m | km }}
!!result
<p>0.01 kilometres
</p>
!! end
!! test
Position and formatting of numbers and units
!! input
*{{#convert: 10 m | km }}
*{{#convert: 10m | km }}
*{{#convert: 10 km | m }}
*{{#convert: 10-km | m }}
*{{#convert: 10E2 km | m }}
*{{#convert: 10E-2 km | m }}
*{{#convert: 10.0E2 km | m }}
*{{#convert: 10.0E2.5 km | m }}
!! result
<ul><li>0.01 kilometres
</li><li>0.01 kilometres
</li><li>10,000 metres
</li><li>10,000- metres
</li><li>1,000,000 metres
</li><li>100 metres
</li><li>1,000,000 metres
</li><li>1,000,000.5,000 metres
</li></ul>
!! end
!! test
Precision 1
!! input
*{{#convert: 10 m | km }}
*{{#convert: 11 m | km }}
*{{#convert: 12 m | km }}
*{{#convert: 13 m | km }}
*{{#convert: 14 m | km }}
*{{#convert: 15 m | km }}
*{{#convert: 16 m | km }}
*{{#convert: 17 m | km }}
*{{#convert: 18 m | km }}
*{{#convert: 19 m | km }}
*{{#convert: 20 m | km }}
!! result
<ul><li>0.01 kilometres
</li><li>0.011 kilometres
</li><li>0.012 kilometres
</li><li>0.013 kilometres
</li><li>0.014 kilometres
</li><li>0.015 kilometres
</li><li>0.016 kilometres
</li><li>0.017 kilometres
</li><li>0.018 kilometres
</li><li>0.019 kilometres
</li><li>0.02 kilometres
</li></ul>
!! end
!! test
Precision 2
!! input
*{{#convert: 10.0 m | km }}
*{{#convert: 10.1 m | km }}
*{{#convert: 10.2 m | km }}
*{{#convert: 10.3 m | km }}
*{{#convert: 10.4 m | km }}
*{{#convert: 10.5 m | km }}
*{{#convert: 10.6 m | km }}
*{{#convert: 10.7 m | km }}
!! result
<ul><li>0.0100 kilometres
</li><li>0.0101 kilometres
</li><li>0.0102 kilometres
</li><li>0.0103 kilometres
</li><li>0.0104 kilometres
</li><li>0.0105 kilometres
</li><li>0.0106 kilometres
</li><li>0.0107 kilometres
</li></ul>
!! end
!! test
String interpolation
!! input
{{#convert: 25, 26, 27, 28, 29, and 30 km }}
!! result
<p>25,000, 26,000, 27,000, 28,000, 29,000, and 30,000 metres
</p>
!! end
!! test
Precision 3
!! input
{{#convert: 25, 26, 27, 28, 29, and 30 miles }}
!! result
<p>40,000, 42,000, 43,000, 45,000, 47,000, and 50,000 metres
</p>
!! end
!! test
Precision 4
!! input
{{#convert:35000, 35E3, 35.0E3, 350E2, 3.500E4, 35000E0, 350000E-1 m | km }}
!! result
<p>35, 35, 35.0, 35, 35.00, 35, 35 kilometres
</p>
!! end
!! test
#sourceunit
!!input
*{{#convert: 25 | #sourceunit = km }}
*{{#convert: 25 | #sourceunit=km }}
*{{#convert: 25 | #sourceunit = km | #sourceunit = mm }}
*{{#convert: 25 | #sourceunit = km | cm }}
!! result
<ul><li>25,000 metres
</li><li>25,000 metres
</li><li>0.025 metres
</li><li>2,500,000 centimetres
</li></ul>
!! end
!! test
Precision overrides
!!input
*{{#convert: 1 mi | #dp = 0 }}
*{{#convert: 1 mi | #dp=1 }}
*{{#convert: 1 mi | #dp = -2 }}
*{{#convert: 1 mi | #dp = 5 }}
*{{#convert: 1 mi | #dp = -8 }}
*{{#convert: 1 mi | #sf = 0 }}
*{{#convert: 1 mi | #sf=1 }}
*{{#convert: 1 mi | #sf = 3 }}
*{{#convert: 1 mi | #sf = 5 }}
*{{#convert: 1 mi | #sf = -8 }}
!! result
<ul><li>1,609 metres
</li><li>1,609.3 metres
</li><li>1,600 metres
</li><li>1,609.344 metres
</li><li>0 metres
</li><li>2,000 metres
</li><li>2,000 metres
</li><li>1,610 metres
</li><li>1,609.3 metres
</li><li>2,000 metres
</li></ul>
!! end
!! test
Errors
!! input
*{{#convert: 25 | km }}
*{{#convert: 25 foobars | mi }}
*{{#convert: 25 mi | #sourceunit = foobar }}
*{{#convert: 25 km | s }}
*{{#convert: 25 km/Pa | m/Pa }}
*{{#convert: 25 km/s/l }}
*{{#convert: 25 km/m3 }}
!! result
<ul><li><strong class="error">Error: no source unit given</strong>
</li><li><strong class="error">Error: unknown unit "foobars"</strong>
</li><li><strong class="error">Error: unknown unit "foobar"</strong>
</li><li><strong class="error">Error: cannot convert between units of "length" and "time"</strong>
</li><li><strong class="error">Error: invalid compound unit "length/pressure"</strong>
</li><li><strong class="error">Error: cannot parse double compound units like "km/s/l"</strong>
</li><li><strong class="error">Error: invalid compound unit "length/volume"</strong>
</li></ul>
!! end
!! test
#sourceunit = #targetunit
!! input
*{{#convert: 25 km | #targetunit = #sourceunit }}
*{{#convert: 25 km | #sourceunit = #targetunit }}
!! result
<ul><li>25 kilometres
</li><li>25 kilometres
</li></ul>
!! end