r81074 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r81073‎ | r81074 | r81075 >
Date:00:13, 27 January 2011
Author:happy-melon
Status:deferred (Comments)
Tags:
Comment:
(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.
Modified paths:
  • /trunk/extensions/ParserFunctions/Convert.php (added) (history)
  • /trunk/extensions/ParserFunctions/ParserFunctions.i18n.magic.php (modified) (history)
  • /trunk/extensions/ParserFunctions/ParserFunctions.i18n.php (modified) (history)
  • /trunk/extensions/ParserFunctions/ParserFunctions.php (modified) (history)
  • /trunk/extensions/ParserFunctions/ParserFunctions_body.php (modified) (history)
  • /trunk/extensions/ParserFunctions/convertTests.txt (added) (history)

Diff [purge]

Index: trunk/extensions/ParserFunctions/convertTests.txt
@@ -0,0 +1,193 @@
 2+!! test
 3+Simple conversion
 4+!! input
 5+{{#convert: 10 m | km }}
 6+!!result
 7+<p>0.01 kilometres
 8+</p>
 9+!! end
 10+
 11+!! test
 12+Position and formatting of numbers and units
 13+!! input
 14+*{{#convert: 10 m | km }}
 15+*{{#convert: 10m | km }}
 16+*{{#convert: 10 km | m }}
 17+*{{#convert: 10-km | m }}
 18+*{{#convert: 10E2 km | m }}
 19+*{{#convert: 10E-2 km | m }}
 20+*{{#convert: 10.0E2 km | m }}
 21+*{{#convert: 10.0E2.5 km | m }}
 22+!! result
 23+<ul><li>0.01 kilometres
 24+</li><li>0.01 kilometres
 25+</li><li>10,000 metres
 26+</li><li>10,000- metres
 27+</li><li>1,000,000 metres
 28+</li><li>100 metres
 29+</li><li>1,000,000 metres
 30+</li><li>1,000,000.5,000 metres
 31+</li></ul>
 32+
 33+!! end
 34+
 35+!! test
 36+Precision 1
 37+!! input
 38+*{{#convert: 10 m | km }}
 39+*{{#convert: 11 m | km }}
 40+*{{#convert: 12 m | km }}
 41+*{{#convert: 13 m | km }}
 42+*{{#convert: 14 m | km }}
 43+*{{#convert: 15 m | km }}
 44+*{{#convert: 16 m | km }}
 45+*{{#convert: 17 m | km }}
 46+*{{#convert: 18 m | km }}
 47+*{{#convert: 19 m | km }}
 48+*{{#convert: 20 m | km }}
 49+!! result
 50+<ul><li>0.01 kilometres
 51+</li><li>0.011 kilometres
 52+</li><li>0.012 kilometres
 53+</li><li>0.013 kilometres
 54+</li><li>0.014 kilometres
 55+</li><li>0.015 kilometres
 56+</li><li>0.016 kilometres
 57+</li><li>0.017 kilometres
 58+</li><li>0.018 kilometres
 59+</li><li>0.019 kilometres
 60+</li><li>0.02 kilometres
 61+</li></ul>
 62+
 63+!! end
 64+
 65+!! test
 66+Precision 2
 67+!! input
 68+*{{#convert: 10.0 m | km }}
 69+*{{#convert: 10.1 m | km }}
 70+*{{#convert: 10.2 m | km }}
 71+*{{#convert: 10.3 m | km }}
 72+*{{#convert: 10.4 m | km }}
 73+*{{#convert: 10.5 m | km }}
 74+*{{#convert: 10.6 m | km }}
 75+*{{#convert: 10.7 m | km }}
 76+!! result
 77+<ul><li>0.0100 kilometres
 78+</li><li>0.0101 kilometres
 79+</li><li>0.0102 kilometres
 80+</li><li>0.0103 kilometres
 81+</li><li>0.0104 kilometres
 82+</li><li>0.0105 kilometres
 83+</li><li>0.0106 kilometres
 84+</li><li>0.0107 kilometres
 85+</li></ul>
 86+
 87+!! end
 88+
 89+!! test
 90+String interpolation
 91+!! input
 92+{{#convert: 25, 26, 27, 28, 29, and 30 km }}
 93+!! result
 94+<p>25,000, 26,000, 27,000, 28,000, 29,000, and 30,000 metres
 95+</p>
 96+!! end
 97+
 98+!! test
 99+Precision 3
 100+!! input
 101+{{#convert: 25, 26, 27, 28, 29, and 30 miles }}
 102+!! result
 103+<p>40,000, 42,000, 43,000, 45,000, 47,000, and 50,000 metres
 104+</p>
 105+!! end
 106+
 107+!! test
 108+Precision 4
 109+!! input
 110+{{#convert:35000, 35E3, 35.0E3, 350E2, 3.500E4, 35000E0, 350000E-1 m | km }}
 111+!! result
 112+<p>35, 35, 35.0, 35, 35.00, 35, 35 kilometres
 113+</p>
 114+!! end
 115+
 116+!! test
 117+#sourceunit
 118+!!input
 119+*{{#convert: 25 | #sourceunit = km }}
 120+*{{#convert: 25 | #sourceunit=km }}
 121+*{{#convert: 25 | #sourceunit = km | #sourceunit = mm }}
 122+*{{#convert: 25 | #sourceunit = km | cm }}
 123+!! result
 124+<ul><li>25,000 metres
 125+</li><li>25,000 metres
 126+</li><li>0.025 metres
 127+</li><li>2,500,000 centimetres
 128+</li></ul>
 129+
 130+!! end
 131+
 132+!! test
 133+Precision overrides
 134+!!input
 135+*{{#convert: 1 mi | #dp = 0 }}
 136+*{{#convert: 1 mi | #dp=1 }}
 137+*{{#convert: 1 mi | #dp = -2 }}
 138+*{{#convert: 1 mi | #dp = 5 }}
 139+*{{#convert: 1 mi | #dp = -8 }}
 140+*{{#convert: 1 mi | #sf = 0 }}
 141+*{{#convert: 1 mi | #sf=1 }}
 142+*{{#convert: 1 mi | #sf = 3 }}
 143+*{{#convert: 1 mi | #sf = 5 }}
 144+*{{#convert: 1 mi | #sf = -8 }}
 145+!! result
 146+<ul><li>1,609 metres
 147+</li><li>1,609.3 metres
 148+</li><li>1,600 metres
 149+</li><li>1,609.344 metres
 150+</li><li>0 metres
 151+</li><li>2,000 metres
 152+</li><li>2,000 metres
 153+</li><li>1,610 metres
 154+</li><li>1,609.3 metres
 155+</li><li>2,000 metres
 156+</li></ul>
 157+
 158+!! end
 159+
 160+
 161+!! test
 162+Errors
 163+!! input
 164+*{{#convert: 25 | km }}
 165+*{{#convert: 25 foobars | mi }}
 166+*{{#convert: 25 mi | #sourceunit = foobar }}
 167+*{{#convert: 25 km | s }}
 168+*{{#convert: 25 km/Pa | m/Pa }}
 169+*{{#convert: 25 km/s/l }}
 170+*{{#convert: 25 km/m3 }}
 171+!! result
 172+<ul><li><strong class="error">Error: no source unit given</strong>
 173+</li><li><strong class="error">Error: unknown unit "foobars"</strong>
 174+</li><li><strong class="error">Error: unknown unit "foobar"</strong>
 175+</li><li><strong class="error">Error: cannot convert between units of "length" and "time"</strong>
 176+</li><li><strong class="error">Error: invalid compound unit "length/pressure"</strong>
 177+</li><li><strong class="error">Error: cannot parse double compound units like "km/s/l"</strong>
 178+</li><li><strong class="error">Error: invalid compound unit "length/volume"</strong>
 179+</li></ul>
 180+
 181+!! end
 182+
 183+
 184+!! test
 185+#sourceunit = #targetunit
 186+!! input
 187+*{{#convert: 25 km | #targetunit = #sourceunit }}
 188+*{{#convert: 25 km | #sourceunit = #targetunit }}
 189+!! result
 190+<ul><li>25 kilometres
 191+</li><li>25 kilometres
 192+</li></ul>
 193+
 194+!! end
Property changes on: trunk/extensions/ParserFunctions/convertTests.txt
___________________________________________________________________
Added: svn:eol-style
1195 + native
Index: trunk/extensions/ParserFunctions/Convert.php
@@ -0,0 +1,735 @@
 2+<?php
 3+
 4+if ( !defined( 'MEDIAWIKI' ) ) {
 5+ die( 'This file is a MediaWiki extension, it is not a valid entry point' );
 6+}
 7+
 8+class ConvertError extends Exception {
 9+ public function __construct( $msg /*...*/ ) {
 10+ $args = func_get_args();
 11+ array_shift( $args );
 12+ $this->message = '<strong class="error">' . wfMsgExt( "pfunc-convert-$msg", 'parseinline', $args ) . '</strong>';
 13+ }
 14+}
 15+
 16+class ConvertParser {
 17+
 18+ # A regex which matches the body of the string and the source unit separately
 19+ const UNITS_REGEX = '/^(.+?)\s*([a-z]+\^?\d?(?:\/\w+\^?\d?)*)$/i';
 20+
 21+ # A regex which matches a number
 22+ const NUM_REGEX = '/\b((?:\+|\-|&minus;|\x2212)?(\d+(?:\.\d+)?)(?:E(?:\+|\-|&minus;|\x2212)?\d+)?)\b/i';
 23+
 24+ # ConvertUnit objects
 25+ protected $sourceUnit;
 26+ protected $targetUnit;
 27+
 28+ # Whether to abbreviate the output unit
 29+ protected $abbreviate;
 30+
 31+ # Whether to link the output unit, if possible
 32+ protected $link;
 33+
 34+ # If set, don't output the unit or format the number
 35+ protected $raw;
 36+
 37+ # What precision to round to.
 38+ protected $decimalPlaces;
 39+ protected $significantFigures;
 40+
 41+ # The last value converted, which will be used for PLURAL evaluation
 42+ protected $lastValue;
 43+
 44+ public function clearState(){
 45+ # Make sure we break any references set up in the parameter passing below
 46+ unset( $this->sourceUnit );
 47+ unset( $this->targetUnit );
 48+ $this->sourceUnit = null;
 49+ $this->targetUnit = null;
 50+
 51+ $this->lastValue
 52+ = $this->link
 53+ = $this->precision
 54+ = $this->abbreviate
 55+ = $this->raw
 56+ = $this->significantFigures
 57+ = $this->decimalPlaces
 58+ = null;
 59+ }
 60+
 61+ /**
 62+ * Evaluate a convert expression
 63+ * @param $args Array of the parameters passed to the original tag function
 64+ * @return String
 65+ * @throws ConvertError
 66+ */
 67+ public function execute( $args ) {
 68+ $this->clearState();
 69+ array_shift( $args ); # Dump Parser object
 70+
 71+ if( count( $args ) == 0 ){
 72+ # that was easy
 73+ return '';
 74+ }
 75+ $string = trim( array_shift( $args ) );
 76+
 77+ # Process the rest of the args
 78+ $sourceUnit =& MagicWord::get( 'sourceunit' );
 79+ $targetUnit =& MagicWord::get( 'targetunit' );
 80+ $linkUnit =& MagicWord::get( 'linkunit' );
 81+ $dp =& MagicWord::get( 'decimalplaces' );
 82+ $sf =& MagicWord::get( 'significantfigures' );
 83+ $abbr =& MagicWord::get( 'abbreviate' );
 84+ $raw =& MagicWord::get( 'rawsuffix' );
 85+
 86+ $n = 0; # Count of unnamed parameters
 87+ foreach ( $args as $arg ) {
 88+ $parts = array_map( 'trim', explode( '=', $arg, 2 ) );
 89+ if ( count( $parts ) == 2 ) {
 90+ # Found "="
 91+ if ( $sourceUnit->matchStartAndRemove( $parts[0] ) ) {
 92+ if( $targetUnit->matchStartAndRemove( $parts[1] ) ){
 93+ $this->targetUnit =& $this->sourceUnit;
 94+ } else {
 95+ $this->sourceUnit = new ConvertUnit( $parts[1] );
 96+ }
 97+
 98+ } elseif ( $targetUnit->matchStartAndRemove( $parts[0] ) ) {
 99+ if( $sourceUnit->matchStartAndRemove( $parts[1] ) ){
 100+ $this->targetUnit =& $this->sourceUnit;
 101+ } else {
 102+ $this->targetUnit = new ConvertUnit( $parts[1] );
 103+ }
 104+
 105+ } elseif( $dp->matchStartAndRemove( $parts[0] ) ) {
 106+ $this->decimalPlaces = intval( $parts[1] );
 107+
 108+ } elseif( $sf->matchStartAndRemove( $parts[0] ) ) {
 109+ # It doesn't make any sense to have negative sig-figs
 110+ if( intval( $parts[1] ) > 0 ){
 111+ $this->significantFigures = intval( $parts[1] );
 112+ }
 113+ }
 114+ } elseif( $linkUnit->matchStartAndRemove( $parts[0] ) ) {
 115+ $this->link = true;
 116+ } elseif( $abbr->matchStartAndRemove( $parts[0] ) ) {
 117+ $this->abbreviate = true;
 118+ } elseif( $raw->matchStartAndRemove( $parts[0] ) ) {
 119+ $this->raw = true;
 120+ } elseif( $parts[0] != '' && !$n++ && !$this->targetUnit instanceof ConvertUnit ){
 121+ # First unnamed parameter = output unit
 122+ $this->targetUnit = new ConvertUnit( $parts[0] );
 123+ }
 124+ }
 125+
 126+ # Get the source unit, if not already set. This throws ConvertError on failure
 127+ if ( !$this->sourceUnit instanceof ConvertUnit ){
 128+ $this->deduceSourceUnit( $string );
 129+ }
 130+
 131+ # Use the default unit (SI usually)
 132+ if( !$this->targetUnit instanceof ConvertUnit ){
 133+ $this->targetUnit = $this->sourceUnit->getDefaultUnit();
 134+ }
 135+
 136+ if( $this->targetUnit->dimension->value != $this->sourceUnit->dimension->value ){
 137+ throw new ConvertError(
 138+ 'dimensionmismatch',
 139+ $this->sourceUnit->dimension->getName(true),
 140+ $this->targetUnit->dimension->getName(true)
 141+ );
 142+ }
 143+
 144+ return $this->processString( $string );
 145+ }
 146+
 147+ protected function deduceSourceUnit( $string ){
 148+ # Get the unit from the end of the string
 149+ $matches = array();
 150+ preg_match( self::UNITS_REGEX, $string, $matches );
 151+
 152+ if( count( $matches ) == 3 ){
 153+ $this->sourceUnit = new ConvertUnit( $matches[2] );
 154+ } else {
 155+ throw new ConvertError( 'nounit' );
 156+ }
 157+ }
 158+
 159+ /**
 160+ * Identify the values to be converted, and convert them
 161+ * @param $string String
 162+ */
 163+ protected function processString( $string ){
 164+ # Replace values
 165+ $string = preg_replace_callback(
 166+ self::NUM_REGEX,
 167+ array( $this, 'convert' ),
 168+ trim( preg_replace( self::UNITS_REGEX, '$1', $string ) )
 169+ );
 170+ if( $this->raw ){
 171+ return $string;
 172+ } else {
 173+ $unit = $this->targetUnit->getText(
 174+ $this->lastValue,
 175+ $this->link,
 176+ $this->abbreviate
 177+ );
 178+ return "$string $unit";
 179+ }
 180+ }
 181+
 182+ /**
 183+ * Express a value in the $sourceUnit in terms of the $targetUnit, preserving
 184+ * an appropriate degree of accuracy.
 185+ * @param $value String
 186+ * @return void
 187+ */
 188+ public function convert( $value ){
 189+ global $wgContLang;
 190+ $valueFloat = floatval( $value[1] );
 191+ $newValue = $valueFloat
 192+ * $this->sourceUnit->getConversion()
 193+ / $this->targetUnit->getConversion();
 194+ if( $this->decimalPlaces !== null && $this->significantFigures !== null ){
 195+ # round to the required number of decimal places, or the required number
 196+ # of significant figures, whichever is the least precise
 197+ $dp = floor( $this->significantFigures - log10( abs( $newValue ) ) ); # Convert SF to DP
 198+ $newValue = round( $newValue, max( $dp, $this->decimalPlaces ) );
 199+
 200+ } elseif( $this->decimalPlaces !== null ){
 201+ $newValue = round( $newValue, $this->decimalPlaces );
 202+
 203+ } elseif( $this->significantFigures !== null ){
 204+ $dp = floor( $this->significantFigures - log10( abs( $newValue ) ) ); # Convert SF to DP
 205+ $newValue = round( $newValue, $dp );
 206+
 207+ } else {
 208+ # Need to round to a similar accuracy as the original value. To do that we
 209+ # select the accuracy which will as closely as possible preserve the maximum
 210+ # percentage error in the value. So 36ft = 36 � 0.5 ft, so the uncertainty
 211+ # is �0.5/36 = �1.4%. In metres this is 10.9728 � 1.4%, or 10.9728 � 0.154
 212+ # we take the stance of choosing the limit which is *more* precise than the
 213+ # original value.
 214+
 215+ # Strip sign and exponent
 216+ $num = preg_replace( self::NUM_REGEX, '$2', $value[1] );
 217+
 218+ if( strpos( $num, '.' ) !== false ){
 219+ # If there is a decimal point, this is the number of digits after it.
 220+ $dpAfter = strlen( $num ) - strpos( $num, '.' ) - 1;
 221+ $error = pow( 10, -$dpAfter - 1 ) * 5;
 222+
 223+ } elseif( $num == 0 ) {
 224+ # The logarithms below will be unhappy, and it doesn't actually matter
 225+ # what error we come up with, zero is still zero
 226+ $error = 1;
 227+
 228+ } else {
 229+ # Number of digits before the point
 230+ $dpBefore = floor( log10( abs( $num ) ) );
 231+
 232+ # Number of digits if we reverse the string = number
 233+ # of digits excluding trailing zeros
 234+ $dpAfter = floor( log10( abs( strrev( $num ) ) ) );
 235+
 236+ # How many significant figures to consider numbers like "35000" to have
 237+ # is a tricky question. We say 2 here because if people want to ensure
 238+ # that the zeros are included, they could write it as 3.500E4
 239+ $error = pow( 10, $dpBefore - $dpAfter - 1 ) * 5;
 240+ }
 241+
 242+ $errorFraction = $error / $num;
 243+
 244+ $i = 10;
 245+ while( $i > -10 && ( round( $newValue, $i - 1 ) != 0 ) &&
 246+ # Rounding to 10dp avoids floating point errors in exact conversions,
 247+ # which are on the order of 1E-16
 248+ ( round( 5 * pow( 10, -$i ) / round( $newValue, $i - 1 ), 10 ) <= round( $errorFraction, 10 ) ) )
 249+ {
 250+ $i--;
 251+ }
 252+
 253+ $newValue = round( $newValue, $i );
 254+ # We may need to stick significant zeros back onto the number
 255+ if( $i > 0 ){
 256+ if( strpos( $newValue, '.' ) !== false ){
 257+ $newValue = str_pad( $newValue, $i + strpos( $newValue, '.' ) + 1, '0' );
 258+ } else {
 259+ $newValue .= '.' . str_repeat( '0', $i );
 260+ }
 261+ }
 262+ }
 263+
 264+ # Store the last value for use in PLURAL later
 265+ $this->lastValue = $newValue;
 266+
 267+ return $this->raw
 268+ ? $newValue
 269+ : $wgContLang->formatNum( $newValue );
 270+ }
 271+
 272+}
 273+
 274+/**
 275+ * A dimension
 276+ */
 277+class ConvertDimension {
 278+
 279+ const MASS = 1; # KILOGRAM
 280+ const LENGTH = 10; # METRE
 281+ const TIME = 100; # SECOND
 282+ const TEMPERATURE = 1E3; # KELVIN
 283+ const QUANTITY = 1E4; # MOLE
 284+ const CURRENT = 1E5; # AMPERE
 285+ const INTENSITY = 1E6; # CANDELA
 286+
 287+ # fuel efficiencies are ugly and horrible and dimensionally confused, and have the
 288+ # same dimensions as LENGTH or 1/LENGTH. But someone wanted to include them... so
 289+ # we have up to ten dimensions which can be identified by values of this.
 290+ # 0 = sane unit
 291+ # 1 = some sort of fuel efficiency
 292+ const UGLY_HACK_VALUE = 1E7;
 293+
 294+ /**
 295+ * Dimension constants. These are the values you'd get if you added the SI
 296+ * base units together with the weighting given above, also the output from
 297+ * getDimensionHash(). Cool thing is, you can add these together to get new
 298+ * compound dimensions.
 299+ */
 300+ const DIM_DIMENSIONLESS = 0; # Numbers etc
 301+ const DIM_LENGTH = 10;
 302+ const DIM_AREA = 20;
 303+ const DIM_VOLUME = 30;
 304+ const DIM_TIME = 100;
 305+ const DIM_TIME_SQ = 200;
 306+ const DIM_MASS = 1;
 307+ const DIM_TEMPERATURE = 1000;
 308+ const DIM_SPEED = -90; # LENGTH / TIME
 309+ const DIM_ACCELERATION = -190; # LENGTH / TIME_SQ
 310+ const DIM_FORCE = -189; # MASS * LENGTH / TIME_SQ
 311+ const DIM_TORQUE = -179; # also MASS * AREA / TIME_SQ, but all units are single
 312+ const DIM_ENERGY = -179; # MASS * AREA / TIME_SQ, all units are compound
 313+ const DIM_PRESSURE = -209; # MASS / ( LENGTH * TIME_SQ )
 314+ const DIM_POWER = -79; # MASS * AREA / TIME
 315+ const DIM_DENSITY = -29; # MASS / VOLUME
 316+ const DIM_FUELEFFICIENCY_PVE = 10000020; # fuel efficiency in VOLUME / LENGTH
 317+ const DIM_FUELEFFICIENCY_NVE = 99999990; # fuel efficiency in LENGTH / VOLUME
 318+
 319+ # Map of dimension names to message keys. This also serves as a list of what
 320+ # dimensions will not throw an error when encountered.
 321+ public static $legalDimensions = array(
 322+ self::DIM_LENGTH => 'length',
 323+ self::DIM_AREA => 'area',
 324+ self::DIM_VOLUME => 'volume',
 325+ self::DIM_TIME => 'time',
 326+ self::DIM_TIME_SQ => 'timesquared',
 327+ self::DIM_MASS => 'mass',
 328+ self::DIM_TEMPERATURE => 'temperature',
 329+ self::DIM_SPEED => 'speed',
 330+ self::DIM_ACCELERATION => 'acceleration',
 331+ self::DIM_FORCE => 'force',
 332+ self::DIM_TORQUE => 'torque',
 333+ self::DIM_ENERGY => 'energy',
 334+ self::DIM_PRESSURE => 'pressure',
 335+ self::DIM_POWER => 'power',
 336+ self::DIM_DENSITY => 'density',
 337+ self::DIM_FUELEFFICIENCY_PVE => 'fuelefficiencypositive',
 338+ self::DIM_FUELEFFICIENCY_NVE => 'fuelefficiencynegative',
 339+ );
 340+
 341+ public $value;
 342+ protected $name;
 343+
 344+ public function __construct( $var, $var2=null ){
 345+ static $legalDimensionsFlip;
 346+
 347+ if( is_string( $var ) ){
 348+ if( $legalDimensionsFlip === null ){
 349+ $legalDimensionsFlip = array_flip( self::$legalDimensions );
 350+ }
 351+ if( isset( $legalDimensionsFlip[$var] ) ){
 352+ $dim = $legalDimensionsFlip[$var];
 353+ } else {
 354+ # Should be unreachable
 355+ throw new ConvertError( 'unknowndimension' );
 356+ }
 357+ } elseif( $var instanceof self ){
 358+ $dim = $var->value;
 359+ } else {
 360+ $dim = intval( $var );
 361+ }
 362+
 363+ if( $var2 === null ){
 364+ $this->value = $dim;
 365+ $this->name = $this->compoundName = self::$legalDimensions[$this->value];
 366+
 367+ } else {
 368+ if( is_string( $var2 ) ){
 369+ if( $legalDimensionsFlip === null ){
 370+ $legalDimensionsFlip = array_flip( self::$legalDimensions );
 371+ }
 372+ if( isset( $legalDimensionsFlip[$var2] ) ){
 373+ $dim2 = $legalDimensionsFlip[$var2];
 374+ } else {
 375+ # Should be unreachable
 376+ throw new ConvertError( 'unknowndimension' );
 377+ }
 378+ } elseif( $var2 instanceof self ){
 379+ $dim2 = $var2->value;
 380+ } else {
 381+ $dim2 = intval( $var2 );
 382+ }
 383+
 384+ $this->value = $dim - $dim2;
 385+ if( in_array( $this->value, array_keys( self::$legalDimensions ) ) ){
 386+ $this->name = self::$legalDimensions[$this->value];
 387+ $this->compoundName = array(
 388+ self::$legalDimensions[$var],
 389+ self::$legalDimensions[$var2],
 390+ );
 391+ } else {
 392+ # Some combinations of units are fine (carats per bushel is a perfectly good,
 393+ # if somewhat bizarre, measure of density, for instance). But others (like
 394+ # carats per miles-per-gallon) are definitely not.
 395+ # TODO: this allows compound units like <gigawatthours>/<pascal> as a unit
 396+ # of volume; is that a good thing or a bad thing?
 397+ throw new ConvertError( 'invalidcompoundunit', "$var/$var2" );
 398+ }
 399+ }
 400+ }
 401+
 402+ /**
 403+ * Convert to string. Magic in PHP 5.1 and above.
 404+ * @return String
 405+ */
 406+ public function __toString(){
 407+ return strval( $this->name );
 408+ }
 409+
 410+ /**
 411+ * Get the name, or names, of the dimension
 412+ * @return String|Array of String
 413+ */
 414+ public function getName( $expandCompound = false ){
 415+ return $expandCompound
 416+ ? $this->name
 417+ : $this->compoundName;
 418+ }
 419+
 420+ /**
 421+ * Get the localised name of the dimension. Output is unescaped
 422+ * @return String
 423+ */
 424+ public function getLocalisedName(){
 425+ return wfMsg( "pfunc-convert-dimension-{$this->name}" );
 426+ }
 427+
 428+}
 429+
 430+class ConvertUnit {
 431+
 432+ /**
 433+ * array(
 434+ * DIMENSION => array(
 435+ * UNIT => array(
 436+ * CONVERSION,
 437+ * REGEX
 438+ * )
 439+ * )
 440+ * )
 441+ */
 442+ protected static $units = array(
 443+ ConvertDimension::DIM_LENGTH => array(
 444+ 'gigametre' => array( 1000000000, 'Gm' ),
 445+ 'megametre' => array( 1000000, '(?:(?-i)Mm)' ), # Case-sensitivity is forced
 446+ 'kilometre' => array( 1000, 'km' ),
 447+ 'hectometre' => array( 100, 'hm' ),
 448+ 'decametre' => array( 10, 'dam' ),
 449+ 'metre' => array( 1, 'm' ),
 450+ 'decimetre' => array( 0.1, 'dm' ),
 451+ 'centimetre' => array( 0.01, 'cm' ),
 452+ 'millimetre' => array( 0.001, '(?:(?-i)mm)' ), # Case-sensitivity is forced
 453+ 'micrometre' => array( 0.0001, '\x03BCm|\x00B5m|um' ), # There are two similar mu characters
 454+ 'nanometre' => array( 0.0000001, 'nm' ),
 455+ 'angstrom' => array( 0.00000001, '\x00C5' ),
 456+
 457+ 'mile' => array( 1609.344, 'mi|miles?' ),
 458+ 'furlong' => array( 201.168, 'furlong' ),
 459+ 'chain' => array( 20.1168 , 'chain' ),
 460+ 'rod' => array( 5.0292, 'rod|pole|perch' ),
 461+ 'fathom' => array( 1.8288, 'fathom' ),
 462+ 'yard' => array( 0.9144, 'yards?|yd' ),
 463+ 'foot' => array( 0.3048, 'foot|feet|ft' ),
 464+ 'hand' => array( 0.1016, 'hands?' ),
 465+ 'inch' => array( 0.0254, 'inch|inches|in' ),
 466+
 467+ 'nauticalmile' => array( 1852, 'nauticalmiles?|nmi' ),
 468+ 'nauticalmileuk' => array( 1853.184, 'oldUKnmi|Brnmi|admi' ),
 469+ 'nauticalmileus' => array( 1853.24496, 'oldUSnmi' ),
 470+
 471+ 'gigaparsec' => array( 3.0856775813057E25, 'gigaparsecs?|Gpc' ),
 472+ 'megaparsec' => array( 3.0856775813057E22, 'megaparsecs?|Mpc' ),
 473+ 'kiloparsec' => array( 3.0856775813057E19, 'kiloparsecs?|kpc' ),
 474+ 'parsec' => array( 3.0856775813057E16, 'parsecs?|pc' ),
 475+ 'gigalightyear' => array( 9.4607304725808E24, 'gigalightyears?|Gly' ),
 476+ 'mrgalightyear' => array( 9.4607304725808E21, 'megalightyears?|Mly' ),
 477+ 'kilolightyear' => array( 9.4607304725808E18, 'kilolightyears?|kly' ),
 478+ 'lightyear' => array( 9.4607304725808E15, 'lightyears?|ly' ),
 479+ 'astronomicalunit' => array( 149597870700, 'astronomicalunits?|AU' ),
 480+ ),
 481+
 482+ ConvertDimension::DIM_AREA => array(
 483+ 'squarekilometre' => array( 1E6, 'km2|km\^2' ),
 484+ 'squaremetre' => array( 1, 'm2|m\^2' ),
 485+ 'squarecentimetre' => array( 1E-4, 'cm2|cm\^2' ),
 486+ 'squaremillimetre' => array( 1E-6, 'mm2|mm\^2' ),
 487+ 'hectare' => array( 1E4, 'hectares?|ha' ),
 488+
 489+ 'squaremile' => array( 2589988.110336, 'sqmi|mi2|mi\^2' ),
 490+ 'acre' => array( 4046.856422 , 'acres?' ),
 491+ 'squareyard' => array( 0.83612736, 'sqyd|yd2|yd\^2' ),
 492+ 'squarefoot' => array( 0.09290304, 'sqft|ft2|ft\^2' ),
 493+ 'squareinch' => array( 0.00064516, 'sqin|in2|in\^2' ),
 494+
 495+ 'squarenauticalmile' => array( 3429904, 'sqnmi|nmi2|nmi\^2' ),
 496+ 'dunam' => array( 1000, 'dunam' ),
 497+ 'tsubo' => array( 3.305785, 'tsubo' ),
 498+ ),
 499+
 500+ ConvertDimension::DIM_VOLUME => array(
 501+ 'cubicmetre' => array( 1, 'm3|m\^3' ),
 502+ 'cubiccentimetre' => array( 1E-6, 'cm3|cm\^3' ),
 503+ 'cubicmillimetre' => array( 1E-9, 'mm3|mm\^3' ),
 504+ 'kilolitre' => array( 1, 'kl' ),
 505+ 'litre' => array( 1E-3 , 'l' ),
 506+ 'centilitre' => array( 1E-5, 'cl' ),
 507+ 'millilitre' => array( 1E-6, 'ml' ),
 508+
 509+ 'cubicyard' => array( 0.764554857984, 'cuyd|yd3|yd\^3' ),
 510+ 'cubicfoot' => array( 0.028316846592, 'cuft|ft3|ft\^3' ),
 511+ 'cubicinch' => array( 0.000016387064, 'cuin|in3|in\^3' ),
 512+ 'barrel' => array( 0.16365924, 'bbl|barrels?|impbbl' ),
 513+ 'bushel' => array( 0.03636872, 'bsh|bushels?|impbsh' ),
 514+ 'gallon' => array( 0.00454609, 'gal|gallons?|impgal' ),
 515+ 'quart' => array( 0.0011365225, 'qt|quarts?|impqt' ),
 516+ 'pint' => array( 0.00056826125, 'pt|pints?|imppt' ),
 517+ 'fluidounce' => array( 0.0000284130625, 'floz|impfloz' ),
 518+
 519+ 'barrelus' => array( 0.119240471196, 'usbbl' ),
 520+ 'barreloil' => array( 0.158987294928, 'oilbbl' ),
 521+ 'barrelbeer' => array( 0.117347765304, 'beerbbl' ),
 522+ 'usgallon' => array( 0.003785411784, 'usgal' ),
 523+ 'usquart' => array( 0.000946352946, 'usqt' ),
 524+ 'uspint' => array( 0.000473176473, 'uspt' ),
 525+ 'usfluidounce' => array( 0.0000295735295625, 'usfloz' ),
 526+ 'usdrybarrel' => array( 0.11562819898508, 'usdrybbl' ),
 527+ 'usbushel' => array( 0.03523907016688, 'usbsh' ),
 528+ 'usdrygallon' => array( 0.00440488377086, 'usdrygal' ),
 529+ 'usdryquart' => array( 0.001101220942715, 'usdryqt' ),
 530+ 'usdrypint' => array( 0.0005506104713575, 'usdrypt' ),
 531+ ),
 532+
 533+ ConvertDimension::DIM_TIME => array(
 534+ 'year' => array( 31557600, 'yr' ),
 535+ 'day' => array( 86400, 'days?' ),
 536+ 'hour' => array( 3600, 'hours?|hr|h' ),
 537+ 'minute' => array( 60, 'minutes?|mins?' ),
 538+ 'second' => array( 1, 's' ),
 539+ ),
 540+
 541+ ConvertDimension::DIM_SPEED => array(
 542+ 'knot' => array( 0.514444444, 'knot|kn' ),
 543+ 'speedoflight' => array( 2.9979E8, 'c' ),
 544+ ),
 545+
 546+ ConvertDimension::DIM_PRESSURE => array(
 547+ 'gigapascal' => array( 1000000000, 'GPa' ),
 548+ 'megapascal' => array( 1000000, '(?:(?-i)M[Pp]a)' ), # Case-sensitivity is forced
 549+ 'kilopascal' => array( 1000, 'kPa' ),
 550+ 'hectopascal' => array( 100, 'hPa' ),
 551+ 'pascal' => array( 1, 'Pa' ),
 552+ 'millipascal' => array( 0.001, '(?:(?-i)m[Pp]a)' ), # Case-sensitivity is forced
 553+
 554+ 'bar' => array( 100000, 'bar' ),
 555+ 'decibar' => array( 10000, 'dbar' ),
 556+ 'milibar' => array( 100 , 'mbar|mb' ),
 557+ 'kilobarye' => array( 100, 'kba' ),
 558+ 'barye' => array( 0.1, 'ba' ),
 559+
 560+ 'atmosphere' => array( 101325, 'atm|atmospheres?' ),
 561+ 'torr' => array( 133.32237, 'torr' ),
 562+ 'mmhg' => array( 133.322387415, 'mmHg' ),
 563+ 'inhg' => array( 3386.38864034, 'inHg' ),
 564+ 'psi' => array( 6894.757293, 'psi' ),
 565+ ),
 566+ # TODO: other dimensions as needed
 567+ );
 568+
 569+ # Default units for each dimension
 570+ # TODO: this should ideally be localisable
 571+ protected static $defaultUnit = array(
 572+ ConvertDimension::DIM_LENGTH => 'metre',
 573+ ConvertDimension::DIM_AREA => 'squaremetre',
 574+ ConvertDimension::DIM_VOLUME => 'cubicmetre',
 575+ ConvertDimension::DIM_TIME => 'second',
 576+ ConvertDimension::DIM_SPEED => 'metre/second',
 577+ ConvertDimension::DIM_PRESSURE => 'pascal',
 578+ );
 579+
 580+ # An array of preprocessing conversions to apply to units
 581+ protected static $unitConversions = array(
 582+ '/^mph$/ui' => 'mi/h',
 583+ );
 584+
 585+ # Map of UNIT => DIMENSION, created on construct
 586+ protected static $dimensionMap = false;
 587+
 588+ /***************** MEMBER VARIABLES *****************/
 589+
 590+ # @var ConvertDimension
 591+ public $dimension;
 592+
 593+ # What number you need to multiply this unit by to get the equivalent
 594+ # value in SI base units
 595+ protected $conversion = 1;
 596+
 597+ # A regex which matches the unit
 598+ protected $regex;
 599+
 600+ # The name of the unit (key into $units[$dimension] above
 601+ protected $unitName;
 602+
 603+ /***************** MEMBER FUNCTIONS *****************/
 604+
 605+ /**
 606+ * Constructor
 607+ * @param $rawUnit String
 608+ */
 609+ public function __construct( $rawUnit ){
 610+ if( self::$dimensionMap === false ){
 611+ self::$dimensionMap = array();
 612+ foreach( self::$units as $dimension => $arr ){
 613+ foreach( $arr as $unit => $val ){
 614+ self::$dimensionMap[$unit] = $dimension;
 615+ }
 616+ }
 617+ }
 618+
 619+ $this->parseUnit( $rawUnit );
 620+ }
 621+
 622+ protected function parseUnit( $rawUnit ){
 623+
 624+ # Do mappings like 'mph' --> 'mi/h'
 625+ $rawUnit = preg_replace(
 626+ array_keys( self::$unitConversions ),
 627+ array_values( self::$unitConversions ),
 628+ $rawUnit
 629+ );
 630+
 631+ $parts = explode( '/', $rawUnit );
 632+ array_map( 'trim', $parts );
 633+ if( count( $parts ) == 1 ){
 634+ # Single unit
 635+ foreach( self::$units as $dimension => $units ){
 636+ foreach( $units as $unit => $data ){
 637+ if( $rawUnit == $unit || preg_match( "/^({$data[1]})$/ui", $parts[0] ) ){
 638+ $this->dimension = new ConvertDimension( self::$dimensionMap[$unit] );
 639+ $this->conversion = self::$units[$this->dimension->value][$unit][0];
 640+ $this->regex = $data[1];
 641+ $this->unitName = $unit;
 642+ return;
 643+ }
 644+ }
 645+ }
 646+
 647+ # Unknown unit
 648+ throw new ConvertError( 'unknownunit', $rawUnit );
 649+
 650+ } elseif( count( $parts ) == 2 ){
 651+ # Compound unit.
 652+ $top = new self( $parts[0] );
 653+ $bottom = new self( $parts[1] );
 654+ $this->dimension = new ConvertDimension( $top->dimension, $bottom->dimension );
 655+ $this->conversion = $top->conversion / $bottom->conversion;
 656+ $this->regex = "(?:{$top->regex})/(?:{$bottom->regex})";
 657+ $this->unitName = array( $top->unitName, $bottom->unitName );
 658+ return;
 659+
 660+ } else {
 661+ # Whaaat? Too many parts
 662+ throw new ConvertError( 'doublecompoundunit', $rawUnit );
 663+ }
 664+ }
 665+
 666+ public function getConversion(){
 667+ return $this->conversion;
 668+ }
 669+
 670+ public function getRegex(){
 671+ return $this->regex;
 672+ }
 673+
 674+ /**
 675+ * Get the text of the unit
 676+ * @param $value String number for PLURAL support
 677+ * @param $link Bool
 678+ * @return String
 679+ */
 680+ public function getText( $value, $link=false, $abbreviate=false ){
 681+ global $wgContLang;
 682+ $value = $wgContLang->formatNum( $value );
 683+ $abbr = $abbreviate ? '-abbr' : '';
 684+
 685+ if( !is_array( $this->unitName ) ){
 686+ $msg = "pfunc-convert-unit-{$this->dimension->getName()}-{$this->unitName}";
 687+ $msgText = wfMsgExt( "$msg$abbr", array( 'parsemag', 'content' ), $value );
 688+ if( $link && !wfEmptyMsg( "$msg-link" ) ){
 689+ $title = Title::newFromText( wfMsgForContentNoTrans( "$msg-link" ) );
 690+ $msgText = "[[{$title->getFullText()}|$msgText]]";
 691+ }
 692+
 693+ } elseif( !wfEmptyMsg( "pfunc-convert-unit-{$this->dimension->getName(true)}-{$this->unitName[0]}-{$this->unitName[1]}" ) ){
 694+ # A wiki has created, say, [[MediaWiki:pfunc-convert-unit-speed-metres-second]]
 695+ # so they can have it display "<metres per second>" rather than
 696+ # "<metres>/<second>"
 697+ $msg = "pfunc-convert-unit-{$this->dimension->getName(true)}-{$this->unitName[0]}-{$this->unitName[1]}";
 698+ $msgText = wfMsgExt( "$msg$abbr", array( 'parsemag', 'content' ), $value );
 699+ if( $link && !wfEmptyMsg( "$msg-link" ) ){
 700+ $title = Title::newFromText( wfMsgForContentNoTrans( "$msg-link" ) );
 701+ if( $title instanceof Title ){
 702+ $msgText = "[[$title|$msgText]]";
 703+ }
 704+ }
 705+
 706+ } else {
 707+ $dimensionNames = $this->dimension->getName();
 708+ $msg = "pfunc-convert-unit-{$dimensionNames[0]}-{$this->unitName[0]}";
 709+ $msgText = wfMsgExt( "$msg$abbr", array( 'parsemag', 'content' ), $value );
 710+ if( $link && !wfEmptyMsg( "$msg-link" ) ){
 711+ $title = Title::newFromText( wfMsgForContentNoTrans( "$msg-link" ) );
 712+ $msgText = "[[{$title->getFullText()}|$msgText]]";
 713+ }
 714+
 715+ $msg2 = "pfunc-convert-unit-{$dimensionNames[1]}-{$this->unitName[1]}";
 716+ $msg2Text = wfMsgExt( "$msg2$abbr", array( 'parsemag', 'content' ), 1 ); # Singular for denominator
 717+ if( $link && !wfEmptyMsg( "$msg2-link" ) ){
 718+ $title = Title::newFromText( wfMsgForContentNoTrans( "$msg2-link" ) );
 719+ if( $title instanceof Title ){
 720+ $msg2Text = "[[{$title->getFullText()}|$msg2Text]]";
 721+ }
 722+ }
 723+ $msgText = "$msgText/$msg2Text";
 724+ }
 725+
 726+ return $msgText;
 727+ }
 728+
 729+ /**
 730+ * Get the default (usually SI) unit associated with this particular dimension
 731+ * @return ConvertUnit
 732+ */
 733+ public function getDefaultUnit(){
 734+ return new ConvertUnit( self::$defaultUnit[$this->dimension->value] );
 735+ }
 736+}
Property changes on: trunk/extensions/ParserFunctions/Convert.php
___________________________________________________________________
Added: svn:eol-style
1737 + native
Index: trunk/extensions/ParserFunctions/ParserFunctions.i18n.magic.php
@@ -22,6 +22,13 @@
2323 'timel' => array( 0, 'timel' ),
2424 'rel2abs' => array( 0, 'rel2abs' ),
2525 'titleparts' => array( 0, 'titleparts' ),
 26+ 'convert' => array( 0, 'convert' ),
 27+ 'sourceunit' => array( 0, '#sourceunit' ),
 28+ 'targetunit' => array( 0, '#targetunit' ),
 29+ 'linkunit' => array( 0, '#linkunit' ),
 30+ 'decimalplaces' => array( 0, '#dp' ),
 31+ 'significantfigures' => array( 0, '#sf' ),
 32+ 'abbreviate' => array( 0, '#abbreviate' ),
2633 'len' => array( 0, 'len' ),
2734 'pos' => array( 0, 'pos' ),
2835 'rpos' => array( 0, 'rpos' ),
Index: trunk/extensions/ParserFunctions/ParserFunctions_body.php
@@ -512,6 +512,29 @@
513513 }
514514 }
515515
 516+ /**
 517+ * Get a ConvertParser object
 518+ * @return ConvertParser
 519+ */
 520+ protected function &getConvertParser() {
 521+ if ( !isset( $this->mConvertParser ) ) {
 522+ if ( !class_exists( 'ConvertParser' ) ) {
 523+ require( dirname( __FILE__ ) . '/Convert.php' );
 524+ }
 525+ $this->mConvertParser = new ConvertParser;
 526+ }
 527+ return $this->mConvertParser;
 528+ }
 529+
 530+ public function convert( /*...*/ ) {
 531+ try {
 532+ $args = func_get_args();
 533+ return $this->getConvertParser()->execute( $args );
 534+ } catch ( ConvertError $e ) {
 535+ return $e->getMessage();
 536+ }
 537+ }
 538+
516539 // Verifies parameter is less than max string length.
517540 private function checkLength( $text ) {
518541 global $wgPFStringLengthLimit;
Index: trunk/extensions/ParserFunctions/ParserFunctions.i18n.php
@@ -27,7 +27,244 @@
2828 'pfunc_expr_invalid_argument_ln' => 'Invalid argument for ln: <= 0',
2929 'pfunc_expr_unknown_error' => 'Expression error: Unknown error ($1)',
3030 'pfunc_expr_not_a_number' => 'In $1: result is not a number',
31 - 'pfunc_string_too_long' => 'Error: String exceeds $1 character limit',
 31+ 'pfunc_string_too_long' => 'Error: string exceeds $1 character limit',
 32+ 'pfunc-convert-dimensionmismatch' => 'Error: cannot convert between units of "$1" and "$2"',
 33+ 'pfunc-convert-unknownunit' => 'Error: unknown unit "$1"',
 34+ 'pfunc-convert-unknowndimension' => 'Error: unknown dimension "$1"',
 35+ 'pfunc-convert-invalidcompoundunit' => 'Error: invalid compound unit "$1"',
 36+ 'pfunc-convert-nounit' => 'Error: no source unit given',
 37+ 'pfunc-convert-doublecompoundunit' => 'Error: cannot parse double compound units like "$1"',
 38+
 39+ # DIMENSION NAMES
 40+ 'pfunc-convert-dimension-length' => 'length',
 41+ 'pfunc-convert-dimension-area' => 'area',
 42+ 'pfunc-convert-dimension-volume' => 'volume',
 43+ 'pfunc-convert-dimension-time' => 'time',
 44+ 'pfunc-convert-dimension-timesquared' => 'time<sup>2</sup>',
 45+ 'pfunc-convert-dimension-mass' => 'mass',
 46+ 'pfunc-convert-dimension-speed' => 'speed',
 47+ 'pfunc-convert-dimension-temperature' => 'temperature',
 48+ 'pfunc-convert-dimension-acceleration' => 'acceleration',
 49+ 'pfunc-convert-dimension-force' => 'force',
 50+ 'pfunc-convert-dimension-torque' => 'torque',
 51+ 'pfunc-convert-dimension-energy' => 'energy',
 52+ 'pfunc-convert-dimension-power' => 'power',
 53+ 'pfunc-convert-dimension-pressure' => 'pressure',
 54+ 'pfunc-convert-dimension-density' => 'density',
 55+ 'pfunc-convert-dimension-fuelefficiencypositive' => 'fuelefficiencypositive',
 56+ 'pfunc-convert-dimension-fuelefficiencynegative' => 'fuelefficiencynegative',
 57+
 58+ # LENGTH
 59+ 'pfunc-convert-unit-length-gigametre' => '{{PLURAL:$1|gigametre|gigametres}}',
 60+ 'pfunc-convert-unit-length-megametre' => '{{PLURAL:$1|megametre|megametres}}',
 61+ 'pfunc-convert-unit-length-kilometre' => '{{PLURAL:$1|kilometre|kilometres}}',
 62+ 'pfunc-convert-unit-length-hectometre' => '{{PLURAL:$1|hectometre|hectometres}}',
 63+ 'pfunc-convert-unit-length-decametre' => '{{PLURAL:$1|decametre|decametres}}',
 64+ 'pfunc-convert-unit-length-metre' => '{{PLURAL:$1|metre|metres}}',
 65+ 'pfunc-convert-unit-length-decimetre' => '{{PLURAL:$1|decimetre|decimetres}}',
 66+ 'pfunc-convert-unit-length-centimetre' => '{{PLURAL:$1|centimetre|centimetres}}',
 67+ 'pfunc-convert-unit-length-millimetre' => '{{PLURAL:$1|millimetre|millimetres}}',
 68+ 'pfunc-convert-unit-length-micrometre' => '{{PLURAL:$1|micrometre|micrometres}}',
 69+ 'pfunc-convert-unit-length-nanometre' => '{{PLURAL:$1|nanometre|nanometres}}',
 70+ 'pfunc-convert-unit-length-angstrom' => '{{PLURAL:$1|angstrom|angstroms}}',
 71+ 'pfunc-convert-unit-length-mile' => '{{PLURAL:$1|mile|miles}}',
 72+ 'pfunc-convert-unit-length-furlong' => '{{PLURAL:$1|furlong|furlongs}}',
 73+ 'pfunc-convert-unit-length-chain' => '{{PLURAL:$1|chain|chains}}',
 74+ 'pfunc-convert-unit-length-rod' => '{{PLURAL:$1|rod|rods}}',
 75+ 'pfunc-convert-unit-length-fathom' => '{{PLURAL:$1|fathom|fathoms}}',
 76+ 'pfunc-convert-unit-length-yard' => '{{PLURAL:$1|yard|yards}}',
 77+ 'pfunc-convert-unit-length-foot' => '{{PLURAL:$1|foot|feet}}',
 78+ 'pfunc-convert-unit-length-hand' => '{{PLURAL:$1|hand|hands}}',
 79+ 'pfunc-convert-unit-length-inch' => '{{PLURAL:$1|inch|inches}}',
 80+ 'pfunc-convert-unit-length-nauticalmile' => '{{PLURAL:$1|nautical mile|nautical miles}}',
 81+ 'pfunc-convert-unit-length-nauticalmileuk' => '{{PLURAL:$1|nautical mile (pre-1970 British)|nautical miles (pre-1970 British)}}',
 82+ 'pfunc-convert-unit-length-nauticalmileus' => '{{PLURAL:$1|nautical mile (pre-1954 US)|nautical miles (pre-1954 US)}}',
 83+ 'pfunc-convert-unit-length-gigaparsec' => '{{PLURAL:$1|gigaparsec|gigaparsecs}}',
 84+ 'pfunc-convert-unit-length-megaparsec' => '{{PLURAL:$1|megaparsec|megaparsecs}}',
 85+ 'pfunc-convert-unit-length-kiloparsec' => '{{PLURAL:$1|kiloparsec|kiloparsecs}}',
 86+ 'pfunc-convert-unit-length-parsec' => '{{PLURAL:$1|parsec|parsecs}}',
 87+ 'pfunc-convert-unit-length-gigalightyear' => '{{PLURAL:$1|gigalightyear|gigalightyears}}',
 88+ 'pfunc-convert-unit-length-mrgalightyear' => '{{PLURAL:$1|megalightyear|megalightyears}}',
 89+ 'pfunc-convert-unit-length-kilolightyear' => '{{PLURAL:$1|kilolightyear|kilolightyears}}',
 90+ 'pfunc-convert-unit-length-lightyear' => '{{PLURAL:$1|lightyear|lightyears}}',
 91+ 'pfunc-convert-unit-length-astronomicalunit' => '{{PLURAL:$1|astronomical unit|astronomical units}}',
 92+
 93+ 'pfunc-convert-unit-length-gigametre-abbr' => 'Gm',
 94+ 'pfunc-convert-unit-length-megametre-abbr' => 'Mm',
 95+ 'pfunc-convert-unit-length-kilometre-abbr' => 'km',
 96+ 'pfunc-convert-unit-length-hectometre-abbr' => 'hm',
 97+ 'pfunc-convert-unit-length-decametre-abbr' => 'dam',
 98+ 'pfunc-convert-unit-length-metre-abbr' => 'm',
 99+ 'pfunc-convert-unit-length-decimetre-abbr' => 'dm',
 100+ 'pfunc-convert-unit-length-centimetre-abbr' => 'cm',
 101+ 'pfunc-convert-unit-length-milimetre-abbr' => 'mm',
 102+ 'pfunc-convert-unit-length-micrometre-abbr' => 'μm',
 103+ 'pfunc-convert-unit-length-nanometre-abbr' => 'nm',
 104+ 'pfunc-convert-unit-length-angstrom-abbr' => 'Å',
 105+ 'pfunc-convert-unit-length-mile-abbr' => 'mi',
 106+ 'pfunc-convert-unit-length-furlong-abbr' => 'furlong',
 107+ 'pfunc-convert-unit-length-chain-abbr' => 'chain',
 108+ 'pfunc-convert-unit-length-rod-abbr' => 'rd',
 109+ 'pfunc-convert-unit-length-fathom-abbr' => 'fathom',
 110+ 'pfunc-convert-unit-length-yard-abbr' => 'yd',
 111+ 'pfunc-convert-unit-length-foot-abbr' => 'ft',
 112+ 'pfunc-convert-unit-length-hand-abbr' => 'h',
 113+ 'pfunc-convert-unit-length-inch-abbr' => 'in',
 114+ 'pfunc-convert-unit-length-nauticalmile-abbr' => 'nmi',
 115+ 'pfunc-convert-unit-length-nauticalmileuk-abbr' => 'nmi (Brit)',
 116+ 'pfunc-convert-unit-length-nauticalmileus-abbr' => 'nmi (pre-1954 US)',
 117+ 'pfunc-convert-unit-length-gigaparsec-abbr' => 'Gpc',
 118+ 'pfunc-convert-unit-length-megaparsec-abbr' => 'Mpc',
 119+ 'pfunc-convert-unit-length-kiloparsec-abbr' => 'kpc',
 120+ 'pfunc-convert-unit-length-parsec-abbr' => 'pc',
 121+ 'pfunc-convert-unit-length-gigalightyear-abbr' => 'Gly',
 122+ 'pfunc-convert-unit-length-mrgalightyear-abbr' => 'Mly',
 123+ 'pfunc-convert-unit-length-kilolightyear-abbr' => 'kly',
 124+ 'pfunc-convert-unit-length-lightyear-abbr' => 'ly',
 125+ 'pfunc-convert-unit-length-astronomicalunit-abbr' => 'AU',
 126+
 127+ # AREA #
 128+ 'pfunc-convert-unit-area-squarekilometre' => '{{PLURAL:$1|square kilometre|square kilometres}}',
 129+ 'pfunc-convert-unit-area-squaremetre' => '{{PLURAL:$1|square metre|square metres}}',
 130+ 'pfunc-convert-unit-area-squarecentimetre' => '{{PLURAL:$1|square centimetre|square centimetres}}',
 131+ 'pfunc-convert-unit-area-squaremillimetre' => '{{PLURAL:$1|square millimetre|square millimetres}}',
 132+ 'pfunc-convert-unit-area-hectare' => '{{PLURAL:$1|hectare|hectares}}',
 133+ 'pfunc-convert-unit-area-squaremile' => '{{PLURAL:$1|square mile|square miles}}',
 134+ 'pfunc-convert-unit-area-acre' => '{{PLURAL:$1|acre|acres}}',
 135+ 'pfunc-convert-unit-area-squareyard' => '{{PLURAL:$1|square yard|square yards}}',
 136+ 'pfunc-convert-unit-area-squarefoot' => '{{PLURAL:$1|square foot|square feet}}',
 137+ 'pfunc-convert-unit-area-squareinch' => '{{PLURAL:$1|square inch|square inches}}',
 138+ 'pfunc-convert-unit-area-squarenauticalmile' => '{{PLURAL:$1|square nautical mile|square nautical miles}}',
 139+ 'pfunc-convert-unit-area-dunam' => '{{PLURAL:$1|dunam|dunams}}',
 140+ 'pfunc-convert-unit-area-tsubo' => '{{PLURAL:$1|tsubo|tsubo}}',
 141+
 142+ 'pfunc-convert-unit-area-squarekilometre-abbr' => 'km<sup>2</sup>',
 143+ 'pfunc-convert-unit-area-squaremetre-abbr' => 'm<sup>2</sup>',
 144+ 'pfunc-convert-unit-area-squarecentimetre-abbr' => 'cm<sup>2</sup>',
 145+ 'pfunc-convert-unit-area-squaremillimetre-abbr' => 'mm<sup>2</sup>',
 146+ 'pfunc-convert-unit-area-hectare-abbr' => 'ha',
 147+ 'pfunc-convert-unit-area-squaremile-abbr' => 'sq mi',
 148+ 'pfunc-convert-unit-area-acre-abbr' => 'acre',
 149+ 'pfunc-convert-unit-area-squareyard-abbr' => 'sq yd',
 150+ 'pfunc-convert-unit-area-squarefoot-abbr' => 'sq ft',
 151+ 'pfunc-convert-unit-area-squareinch-abbr' => 'sq in',
 152+ 'pfunc-convert-unit-area-squarenauticalmile-abbr' => 'sq nmi',
 153+ 'pfunc-convert-unit-area-dunam-abbr' => 'dunam',
 154+ 'pfunc-convert-unit-area-tsubo-abbr' => 'tsubo',
 155+
 156+ # TIME #
 157+ 'pfunc-convert-unit-time-second' => '{{PLURAL:$1|second|seconds}}',
 158+ 'pfunc-convert-unit-time-year' => '{{PLURAL:$1|year|years}}',
 159+ 'pfunc-convert-unit-time-day' => '{{PLURAL:$1|day|days}}',
 160+ 'pfunc-convert-unit-time-hour' => '{{PLURAL:$1|hour|hours}}',
 161+ 'pfunc-convert-unit-time-minute' => '{{PLURAL:$1|minute|minutes}}',
 162+
 163+ 'pfunc-convert-unit-time-second-abbr' => 's',
 164+ 'pfunc-convert-unit-time-year-abbr' => 'yr',
 165+ 'pfunc-convert-unit-time-day-abbr' => 'day',
 166+ 'pfunc-convert-unit-time-hour-abbr' => 'hr',
 167+ 'pfunc-convert-unit-time-minute-abbr' => 'min',
 168+
 169+ # VOLUME #
 170+ 'pfunc-convert-unit-volume-cubicmetre' => '{{PLURAL:$1|cubic metre|cubic metres}}',
 171+ 'pfunc-convert-unit-volume-cubiccentimetre' => '{{PLURAL:$1|cubic centimetre|cubic centimetres}}',
 172+ 'pfunc-convert-unit-volume-cubicmillimetre' => '{{PLURAL:$1|cubic millimetre|cubic millimetres}}',
 173+ 'pfunc-convert-unit-volume-kilolitre' => '{{PLURAL:$1|kilolitre|kilolitres}}',
 174+ 'pfunc-convert-unit-volume-litre' => '{{PLURAL:$1|litre|litres}}',
 175+ 'pfunc-convert-unit-volume-centilitre' => '{{PLURAL:$1|centilitre|centilitres}}',
 176+ 'pfunc-convert-unit-volume-millilitre' => '{{PLURAL:$1|millilitre|millilitres}}',
 177+ 'pfunc-convert-unit-volume-cubicyard' => '{{PLURAL:$1|cubic yard|cubic yards}}',
 178+ 'pfunc-convert-unit-volume-cubicfoot' => '{{PLURAL:$1|cubic foot|cubic feet}}',
 179+ 'pfunc-convert-unit-volume-cubicinch' => '{{PLURAL:$1|cubic inch|cubic inches}}',
 180+ 'pfunc-convert-unit-volume-barrel' => '{{PLURAL:$1|barrel|barrels}}',
 181+ 'pfunc-convert-unit-volume-bushel' => '{{PLURAL:$1|bushel|bushels}}',
 182+ 'pfunc-convert-unit-volume-gallon' => '{{PLURAL:$1|gallon|gallons}}',
 183+ 'pfunc-convert-unit-volume-quart' => '{{PLURAL:$1|quart|quarts}}',
 184+ 'pfunc-convert-unit-volume-pint' => '{{PLURAL:$1|pint|pints}}',
 185+ 'pfunc-convert-unit-volume-fluidounce' => '{{PLURAL:$1|fluid ounce|fluid ounces}}',
 186+ 'pfunc-convert-unit-volume-barrelus' => '{{PLURAL:$1|US barrel|US barrels}}',
 187+ 'pfunc-convert-unit-volume-barreloil' => '{{PLURAL:$1|barrel|barrel}}',
 188+ 'pfunc-convert-unit-volume-barrelbeer' => '{{PLURAL:$1|barrel|barrel}}',
 189+ 'pfunc-convert-unit-volume-usgallon' => '{{PLURAL:$1|US gallon|US gallons}}',
 190+ 'pfunc-convert-unit-volume-usquart' => '{{PLURAL:$1|US quart|US quarts}}',
 191+ 'pfunc-convert-unit-volume-uspint' => '{{PLURAL:$1|US pint|US pints}}',
 192+ 'pfunc-convert-unit-volume-usfluidounce' => '{{PLURAL:$1|US fluid ounce|US fluid ounces}}',
 193+ 'pfunc-convert-unit-volume-usdrybarrel' => '{{PLURAL:$1|US dry barrel|US dry barrels}}',
 194+ 'pfunc-convert-unit-volume-usbushel' => '{{PLURAL:$1|US bushel|US bushels}}',
 195+ 'pfunc-convert-unit-volume-usdrygallon' => '{{PLURAL:$1|US dry gallon|US dry gallons}}',
 196+ 'pfunc-convert-unit-volume-usdryquart' => '{{PLURAL:$1|US dry quart|US dry quarts}}',
 197+ 'pfunc-convert-unit-volume-usdrypint' => '{{PLURAL:$1|US dry pint|US dry pints}}',
 198+
 199+ 'pfunc-convert-unit-volume-cubicmetre-abbr' => 'm<sup>3</sup>',
 200+ 'pfunc-convert-unit-volume-cubiccentimetre-abbr' => 'cm<sup>3</sup>',
 201+ 'pfunc-convert-unit-volume-cubicmillimetre-abbr' => 'mm<sup>3</sup>',
 202+ 'pfunc-convert-unit-volume-kilolitre-abbr' => 'kl',
 203+ 'pfunc-convert-unit-volume-litre-abbr' => 'l',
 204+ 'pfunc-convert-unit-volume-centilitre-abbr' => 'cl',
 205+ 'pfunc-convert-unit-volume-millilitre-abbr' => 'ml',
 206+ 'pfunc-convert-unit-volume-cubicyard-abbr' => 'cu yd',
 207+ 'pfunc-convert-unit-volume-cubicfoot-abbr' => 'cu ft',
 208+ 'pfunc-convert-unit-volume-cubicinch-abbr' => 'cu in',
 209+ 'pfunc-convert-unit-volume-barrel-abbr' => 'bbl',
 210+ 'pfunc-convert-unit-volume-bushel-abbr' => 'bsh',
 211+ 'pfunc-convert-unit-volume-gallon-abbr' => 'gal',
 212+ 'pfunc-convert-unit-volume-quart-abbr' => 'qt',
 213+ 'pfunc-convert-unit-volume-pint-abbr' => 'pt',
 214+ 'pfunc-convert-unit-volume-fluidounce-abbr' => 'fl oz',
 215+ 'pfunc-convert-unit-volume-barrelus-abbr' => 'US bbl',
 216+ 'pfunc-convert-unit-volume-barreloil-abbr' => 'bbl',
 217+ 'pfunc-convert-unit-volume-barrelbeer-abbr' => 'bbl',
 218+ 'pfunc-convert-unit-volume-usgallon-abbr' => 'US gal',
 219+ 'pfunc-convert-unit-volume-usquart-abbr' => 'US qt',
 220+ 'pfunc-convert-unit-volume-uspint-abbr' => 'US pt',
 221+ 'pfunc-convert-unit-volume-usfluidounce-abbr' => 'US fl oz',
 222+ 'pfunc-convert-unit-volume-usdrybarrel-abbr' => 'US bbl',
 223+ 'pfunc-convert-unit-volume-usbushel-abbr' => 'US bsh',
 224+ 'pfunc-convert-unit-volume-usdrygallon-abbr' => 'US dry gal',
 225+ 'pfunc-convert-unit-volume-usdryquart-abbr' => 'US dry qt',
 226+ 'pfunc-convert-unit-volume-usdrypint-abbr' => 'US dry pt',
 227+
 228+ # SPEED
 229+ 'pfunc-convert-unit-speed-mile-hour' => 'miles per hour',
 230+ 'pfunc-convert-unit-speed-speedoflight' => 'c',
 231+
 232+ 'pfunc-convert-unit-speed-mile-hour-abbr' => 'mph',
 233+ 'pfunc-convert-unit-speed-speedoflight-abbr' => 'c',
 234+
 235+ # PRESSURE
 236+ 'pfunc-convert-unit-pressure-gigapascal' => '{{PLURAL:$1|gigapascal|gigapascals}}',
 237+ 'pfunc-convert-unit-pressure-megapascal' => '{{PLURAL:$1|megapascal|megapascals}}',
 238+ 'pfunc-convert-unit-pressure-kilopascal' => '{{PLURAL:$1|kilopascal|kilopascals}}',
 239+ 'pfunc-convert-unit-pressure-hectopascal' => '{{PLURAL:$1|hectopascal|hectopascals}}',
 240+ 'pfunc-convert-unit-pressure-pascal' => '{{PLURAL:$1|pascal|pascals}}',
 241+ 'pfunc-convert-unit-pressure-millipascal' => '{{PLURAL:$1|millipascal|millipascals}}',
 242+ 'pfunc-convert-unit-pressure-bar' => 'bar',
 243+ 'pfunc-convert-unit-pressure-decibar' => 'decibar',
 244+ 'pfunc-convert-unit-pressure-millibar' => 'millibar',
 245+ 'pfunc-convert-unit-pressure-kilobarye' => 'kilobarye',
 246+ 'pfunc-convert-unit-pressure-barye' => 'barye',
 247+ 'pfunc-convert-unit-pressure-atmosphere' => '{{PLURAL:$1|atmosphere|atmospheres}}',
 248+ 'pfunc-convert-unit-pressure-torr' => '{{PLURAL:$1|Torr|Torr}}',
 249+ 'pfunc-convert-unit-pressure-mmhg' => '{{PLURAL:$1|milimetre of mercury|milimetres of mercury}}',
 250+ 'pfunc-convert-unit-pressure-inhg' => '{{PLURAL:$1|inch of mercury|inches of mercury}}',
 251+ 'pfunc-convert-unit-pressure-psi' => '{{PLURAL:$1|pound per square-inch|pounds per square-inch}}',
 252+
 253+ 'pfunc-convert-unit-pressure-gigapascal-abbr' => 'GPa',
 254+ 'pfunc-convert-unit-pressure-megapascal-abbr' => 'MPa',
 255+ 'pfunc-convert-unit-pressure-kilopascal-abbr' => 'kPa',
 256+ 'pfunc-convert-unit-pressure-hectopascal-abbr' => 'hPa',
 257+ 'pfunc-convert-unit-pressure-pascal-abbr' => 'Pa',
 258+ 'pfunc-convert-unit-pressure-millipascal-abbr' => 'mPa',
 259+ 'pfunc-convert-unit-pressure-bar-abbr' => 'bar',
 260+ 'pfunc-convert-unit-pressure-decibar-abbr' => 'dbar',
 261+ 'pfunc-convert-unit-pressure-milibar-abbr' => 'mbar',
 262+ 'pfunc-convert-unit-pressure-kilobarye-abbr' => 'kBa',
 263+ 'pfunc-convert-unit-pressure-barye-abbr' => 'Ba',
 264+ 'pfunc-convert-unit-pressure-atmosphere-abbr' => 'atm',
 265+ 'pfunc-convert-unit-pressure-torr-abbr' => 'Torr',
 266+ 'pfunc-convert-unit-pressure-mmhg-abbr' => 'mmHg',
 267+ 'pfunc-convert-unit-pressure-inhg-abbr' => 'inHg',
 268+ 'pfunc-convert-unit-pressure-psi-abbr' => 'psi',
32269 );
33270
34271 /** Message documentation (Message documentation)
Index: trunk/extensions/ParserFunctions/ParserFunctions.php
@@ -46,6 +46,7 @@
4747
4848 $wgParserTestFiles[] = dirname( __FILE__ ) . "/funcsParserTests.txt";
4949 $wgParserTestFiles[] = dirname( __FILE__ ) . "/stringFunctionTests.txt";
 50+$wgParserTestFiles[] = dirname( __FILE__ ) . "/convertTests.txt";
5051
5152 function wfSetupParserFunctions() {
5253 global $wgPFHookStub, $wgHooks;
@@ -89,6 +90,7 @@
9091 $parser->setFunctionHook( 'timel', array( &$this, 'localTime' ) );
9192 $parser->setFunctionHook( 'rel2abs', array( &$this, 'rel2abs' ) );
9293 $parser->setFunctionHook( 'titleparts', array( &$this, 'titleparts' ) );
 94+ $parser->setFunctionHook( 'convert', array( &$this, 'convert' ) );
9395
9496 // String Functions
9597 if ( $wgPFEnableStringFunctions ) {

Sign-offs

UserFlagDate
Aaron Schulzinspected20:44, 15 August 2011

Follow-up revisions

RevisionCommit summaryAuthorDate
r81085Temporarly disabled due to r81074. This revision adds 217 (!) new messages an...raymond08:41, 27 January 2011
r81095Follow-up r81074 CR:...happy-melon17:34, 27 January 2011
r81189Follow-up r81074:...happy-melon13:54, 29 January 2011
r81190Follow-up r81074: succumb, though it breaks my heart to do so (:P), to Americ...happy-melon15:45, 29 January 2011
r81191Follow-up r81074: better handling of whitespace, or lack thereof, between the...happy-melon15:59, 29 January 2011
r81517Follow-up r81074: turns out {{convert}} is (mostly) case-sensitive after all,...happy-melon14:28, 4 February 2011
r82230Follow-up r81074, r81517: implement SI prefixes as a separate structure. You...happy-melon10:27, 16 February 2011
r95525Follow-up r81074: fix typos in some unit names and messages ("mili" instead o...happy-melon21:22, 25 August 2011
r96499Follow-up r81074:...happy-melon21:06, 7 September 2011
r96502Follow up r81074: implement a language map per Tim's suggestion (no more Amer...happy-melon21:28, 7 September 2011
r96505FU 96499, r81074: fix parser tests to reflect the change in default dialect, ...happy-melon21:39, 7 September 2011

Comments

#Comment by Nikerabbit (talk | contribs)   08:43, 27 January 2011

Few comments:

  • The file should be in utf-8 encoding
  • Why doesn't getConvertParser use autoloader?
  • Extra tabs in ParserFunctions.i18n.magic.php
  • getLocalisedName() is never used, but if it will, returning the object returned by wfMessage would allow more flexible use.

This only works in PHP 5.2 and newer:

 +	if( $title instanceof Title ){
 +		$msgText = "[[$title|$msgText]]";

Maybe that and the few lines around it could be refactored into method?

#Comment by 😂 (talk | contribs)   14:13, 27 January 2011

I thought we discussed trunk can feel free to break 5.1 compat?

#Comment by Nikerabbit (talk | contribs)   14:23, 27 January 2011

I thought there was supposed to be a version that required 5.1 or newer before that.

#Comment by 😂 (talk | contribs)   14:26, 27 January 2011

Last I remember discussing, we said 1.17 should still retain 5.1 compat, but trunk was fine for breaking.

This doesn't have to be merged to the 1.17 branch.

#Comment by Happy-melon (talk | contribs)   17:10, 27 January 2011

I'm firmly in the camp that we can break back-compat in this fashion, but there's no particular reason why it needs to; especially in an extension which might well be upgraded independently of the core software. I wouldn't bat an eyelid about doing this in core trunk, but since it's so easy to avoid here, I'll do so.

#Comment by Happy-melon (talk | contribs)   17:34, 27 January 2011

The file should be in utf-8 encoding

/me hits PhpStorm with a hammer until it coughs up UTF8 encoding...

Why doesn't getConvertParser use autoloader?

That function was cloned directly from getExprParser() above; I've implemented autoloader for both of them in r81095.

Extra tabs in ParserFunctions.i18n.magic.php

They were deliberate to separate the 'subwords' out, but meh... :D

getLocalisedName() is never used, but if it will, returning the object returned by wfMessage would allow more flexible use.

It was supposed to be used in the "dimension mismatch" error; now implemented.

#Comment by Siebrand (talk | contribs)   12:27, 27 January 2011

Unit names are in enGB and should be in enUS, because our default localisation is in American English. Example: metre -> meter.

#Comment by Happy-melon (talk | contribs)   15:46, 29 January 2011

Done, with great bitterness (:P) in r81190.

#Comment by Siebrand (talk | contribs)   21:29, 29 January 2011

We could of course dump enGB and create enUS...

#Comment by Happy-melon (talk | contribs)   23:08, 29 January 2011

I'd call "en" 'English (international)', which would tend to mean that the spelling which is used in every English-speaking country apart from one, would be the one considered "international"...? :P

#Comment by Raymond (talk | contribs)   16:23, 27 January 2011

I have set the new messages to "ignore" on translatewiki to avoid wasted time of our translators in case of a revert. Any chance to get an OK from a lead developer in the near future?

#Comment by Happy-melon (talk | contribs)   17:08, 27 January 2011

Eventually... :P ~~~~

#Comment by Bryan (talk | contribs)   10:48, 28 January 2011

You really shouldn't hardcode the SI prefixes. The SI prefixes are consistent, so you can easily deduce that kPa means 10^3 Pa. Similarly, a square inch is 0.0254^2 meters, so you only need to hardcode the inch to meter conversion and all other can be derived from that.

Furthermore all exceptions should derive from MwException.

#Comment by Happy-melon (talk | contribs)   16:42, 28 January 2011

But a kn is not a kilonewton, it's a knot (actually this is itself a redefinition; the official abbreviation for the knot is 'kt', which conflicts with kiloton); a pc is not a picocoulomb, it's a parsec; and yd is neither a yoctoday nor a yoctodebye. All of those could be handled by forcing case-sensitivity, but that's a significant deviation from the current behaviour of {{convert}}. Then there's the question of what base units should take prefixes, and which ones: a kilomile makes sense, but a nanomile should actually be a nautical mile; should we accept nanotons of TNT or nanolightyears?

If this only handled SI units, there would be no problem, as the SI is carefully thought through to avoid such conflicts; the huge number of non-SI units in play, for which no such thought has been given, makes it much more dangerous to employ automated extraction of scales and powers like this. An acre, for instance, is not the square of one imperial unit, but 1 chain * 1 furlong. In general, in the process of making it as object-oriented as possible I tried to make it a bit more intelligent than just pure number-crunching, but I'm not sure if this would be too intelligent for its own good.

The ConvertError code was cloned directly from ExprError, so if one needs fixing, so does the other.

#Comment by Bryan (talk | contribs)   17:54, 28 January 2011

Fair enough, I would say that case sensitivity is not an unreasonable requirement for a unit, but I can see the argument of {{convert}} compatibility.

(I have nevery heard of kn as abbreviation for knot btw, as far as I'm aware it is always kt, but I'm aviation biased.

#Comment by Happy-melon (talk | contribs)   22:11, 28 January 2011

Indeed; kt is the approved abbreviation; the convert template instead used kn precisely because it chose to ignore case sensitivity...

#Comment by The ed17 (talk | contribs)   05:12, 1 February 2011

As a ships person on enwiki, I'll be ecstatic when this is implemented, because we tend to convert a lot of units. Even if you have to input "kn" to get knots, can it output "kt" on pages so we have the correct abbreviation?

#Comment by Happy-melon (talk | contribs)   13:55, 3 February 2011

Actually, it seems {{convert}} is (mostly) case-sensitive after all, so this might be possible, with some care.

#Comment by Tim Starling (talk | contribs)   04:00, 6 September 2011

The reason I made ExprError derive from Exception instead of MWException is because ExprErrors are never allowed to propagate to the calling code, they are always caught and converted to an error message. So they are private to the extension and do not need the MediaWiki-specific formatting code in MWException.

#Comment by Duplicatebug (talk | contribs)   14:22, 29 January 2011

return "$string $unit";: hardcoded whitespace is bad in some languages. Use MediaWiki:Word-separator. Or move the number into the message, and allow a second message (suffix -link) with wikilink inside the message (Lego is not nice...), but than you need also a message for link and abbr...

#Comment by Happy-melon (talk | contribs)   15:59, 29 January 2011

Actually, there shouldn't be whitespace added there at all, it should use whatever's in the source string. Done in r81191.

#Comment by SimonTrew (talk | contribs)   16:54, 2 February 2011

There are two similar characters for Angstrom: U+OOC5 LATIN CAPITAL LETTER A WITH RING ABOVE and U+212B ANGSTROM SIGN. Can this be handled similarly to how "mu" is handled? (Some fonts do render them a little differently.) ~~~~

#Comment by Bawolff (talk | contribs)   19:42, 3 February 2011

Mediawiki normalizes U+212B to U+00C5 before this extension would even see it, so I don't think that's an issue.


However, in the regexes, things like \x00C5 need to be \x{00C5} i believe.

#Comment by SimonTrew (talk | contribs)   03:05, 4 February 2011

Thank you for that pertinent reply. I am not familiar with WikiMedia, being mosly on Wikipedia. All is good then, and I hope my question was not out of order, at least we made a precedent together! My best wishes.

#Comment by SimonTrew (talk | contribs)   03:15, 4 February 2011

And apparently I can't even tell the difference between MediaWiki and WikiMedia, my bad. I have had quite a few odds and sods with the Template:Convert template though, which I use quite regularly, indeed have added a few little units to it myself, and am a little worried ok this will deal with one-to-one units but how does it deal with for example mm into in cm and vice versa, or km into mi ch (miles and chains as used on British railways and pretty much everywhere else the British Empire set foot, even Belgium bizarelly enough). It is that kind of quagmire you are going to get bogged down into with an all-fits-one solution. I don't think you are proposing that really but Template:Convert will not go away, just the jumping through hoops for simple arithmetic will go but the parsing will probably be half and half between that template and this extension. I note, for example, that the deduction of precision is nothing like as good as Template:Convert. User:Jimp is master there and you should appreciate his views. He would be quite happy to ditch it all I am sure bu after years of struggling he knows well enough one size does not fit all.

#Comment by Happy-melon (talk | contribs)   14:50, 4 February 2011

Bipartite units are indeed a challenge, but can still be dealt with much more efficiently using this function. For instance,

{{#expr: {{#convert: {{{1}}} | R }} + {{#convert: {{{2}}} | R }} }}

Combines two values and expresses them in the base unit of the relevant dimension, so it would convert {{foo|2 ft|3 in}} into "0.68" (metres), which can then be input to another layer of {{#convert:...}} functions to get the desired output. The design of this function is precisely to avoid trying to build a complete solution; on-wiki templates still have a part to play in presentation and layout. But that should be achievable through one template, not three thousand.

The algorithm used to establish precision here is different, but that doesn't necessarily make it inferior; and the algorithm used in {{[[

en:Template:convert|convert]]}} has numerous holes and inconsistencies (an inevitable consequence of the limited parsing support). I've started a discussion on en:Template talk:Convert to compare the algorithms used, to see if there's anything which could be improved.
#Comment by Happy-melon (talk | contribs)   20:13, 16 February 2011

I think all the existing comments on this have been addressed, so setting back to new.

#Comment by Siebrand (talk | contribs)   08:31, 17 August 2011

There are typos in messages and key names with regards to "milli" that is somtimes written as "mili". I fear this may also cause functional issues.

#Comment by Happy-melon (talk | contribs)   21:23, 25 August 2011

Fixed in r95525.

#Comment by SimonTrew (talk | contribs)   06:38, 18 August 2011

I'm not sure that dimensional analysis is helpful — actually it is sometimes positively harmful. People for example measure their tyre pressure in pounds per square inch, although dimensionally it is foot-pounds per square inch, nobody actually says that in real life. (Sweeping statement there I know.) Another interesting one is miles per gallon versus litres per hundred kilometres, where it is a reciprocal conversion not a multiplication. In the UK, everyone including the auto companies quotes in miles per gallon, even though in the small print it will be in l/100km, and we buy our fuel in litres. I note also the difference in US and UK spelling for liter/litre, meter/metre etc, and wonder how this is coped with. It doesn't matter so much for EN:WP but for WIkimedia which has to support all languages perhaps is relevant — as I said above, as a wikignome on EN:WP I am not very well up on the scope of Wikimedia, so I can imagine wiser people than I have already thought of this kind of thing. ~~~~

#Comment by Tim Starling (talk | contribs)   03:50, 6 September 2011

Actually the measure of tyre pressure is pounds-force per square inch, not foot-pounds per square inch. See w:Pounds per square inch. Dimensional analysis is a useful technique, it would have identified your error here. Dimensional analysis can cope with reciprocals.

Spelling differences are best dealt with by using abbreviations, which are consistent and international. A style guide I once read recommended that abbreviations should always be used in preference to the unit names, since this convention avoids an unfortunate ambiguity between rad as an abbreviation for radians, and rad the unit name which is abbreviated to rd. Also they are shorter. However {{convert}} on Wikipedia uses full names by default so I suppose we should follow suit here.

It seems kind of strange to adopt the spelling conventions for metric units from the most obstinate anti-metric country in the world as our default. The Wikipedia templates do not take this approach, they use the British spelling by default, and allow an "sp=us" parameter to access the American spelling.

I suggest adding a method for configuring the language variant to be used on a wiki for unit name display. This would allow the new parser function to match the style used on Wikipedia and avoid the need for #language=en-gb to be added to every invocation. Setting "fixme" status for this feature request.

Ideally it should provide a map of base languages to sensible defaults, to provide Wikipedia-like functionality to small wikis without configuration, e.g.:

$wgPFUnitLanguageVariants = array(
   'en' => 'en-gb'
);

If $wgContLang is present as a key in the array, the language in the value of that element would be used by default for ConvertParser::$language.

#Comment by Tim Starling (talk | contribs)   04:57, 6 September 2011

Please add doc comments to the functions that lack them.

On line 421:

					self::$legalDimensions[$var],
					self::$legalDimensions[$var2],

Presumably this is meant to be $dim and $dim2. I get an "illegal offset type" warning because $var is an object, with test case {{#convert:1 kn|km/h}}. And with the same test case, on line 728:

	return $this->prefix
		? $this->conversion * self::$prefixes[$this->prefix][0]
		: $this->conversion;

This is broken with compound units, where $this->prefix is an array.

{{#convert:1 km| mi}} gives "1 mile". The correct answer to one significant figure would be 0.6 miles, so something is going wrong with the precision calculations here. Note that when you express a value between 1 and 2 with one significant figure, it's conventional to add another decimal place, e.g. 1.2 instead of 1.

I'm sorry to say, but these kinds of errors suggest that this module has not been sufficiently well-tested to justify immediate deployment to Wikimedia. I think registration of the #convert parser function should be made optional, and disabled by default for now, unless a lot of extra work is done.

Status & tagging log