summaryrefslogtreecommitdiff
path: root/extensions/ParserFunctions/Convert.php
diff options
context:
space:
mode:
Diffstat (limited to 'extensions/ParserFunctions/Convert.php')
-rw-r--r--extensions/ParserFunctions/Convert.php907
1 files changed, 0 insertions, 907 deletions
diff --git a/extensions/ParserFunctions/Convert.php b/extensions/ParserFunctions/Convert.php
deleted file mode 100644
index cdfd2232..00000000
--- a/extensions/ParserFunctions/Convert.php
+++ /dev/null
@@ -1,907 +0,0 @@
-<?php
-
-if ( !defined( 'MEDIAWIKI' ) ) {
- die( 'This file is a MediaWiki extension, it is not a valid entry point' );
-}
-
-class ConvertError extends MWException {
- public function __construct( $msg /*...*/ ) {
- $args = func_get_args();
- array_shift( $args );
- array_map( 'htmlspecialchars', $args );
- $this->message = '<strong class="error">' . wfMsgForContent( "pfunc-convert-$msg", $args ) . '</strong>';
- }
-}
-
-class ConvertParser {
-
- # A regex which matches the body of the string and the source unit separately
- const UNITS_REGEX = '/^(.+?)([a-z]+\^?\d?(?:\/\w+\^?\d?)*)$/i';
-
- # A regex which matches a number
- const NUM_REGEX = '/\b((?:\+|\-|&minus;|\x{2212})?(\d+(?:\.\d+)?)(?:E(?:\+|\-|&minus;|\x{2212})?\d+)?)\b/iu';
-
- # A regex *FRAGMENT* which matches SI prefixes
- const PREFIX_REGEX = '[YZEPTGMkh(da)dcm\x{03BC}\x{00B5}npfazy]?';
-
- /**
- * @var ConvertUnit
- */
- protected $sourceUnit;
-
- /**
- * @var ConvertUnit
- */
- 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;
-
- # What language to display the units in
- # @var Language
- protected $language;
-
- # The last value converted, which will be used for PLURAL evaluation
- protected $lastValue;
-
- protected $precision;
-
- /**
- * Reset the parser so it isn't contaminated by the results of previous parses
- */
- 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;
-
- $this->language = true; # prompts wfGetLangObj() to use $wgContLang
- }
-
- /**
- * 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
- static $magicWords = array(
- 'sourceunit' => null,
- 'targetunit' => null,
- 'linkunit' => null,
- 'decimalplaces' => null,
- 'significantfigures' => null,
- 'abbreviate' => null,
- 'rawsuffix' => null,
- 'language' => null,
- );
- if( !is_object( $magicWords ) ){
- foreach( $magicWords as $key => &$val ){
- $magicWords[$key] =& MagicWord::get( $key );
- }
- # The $magicWords[key]->function() syntax doesn't work, so cast to
- # object so we can use $magicWords->key->function() instead
- $magicWords = (object)$magicWords;
- }
-
- $n = 0; # Count of unnamed parameters
- foreach ( $args as $arg ) {
- $parts = array_map( 'trim', explode( '=', $arg, 2 ) );
- if ( count( $parts ) == 2 ) {
- # Found "="
- if ( $magicWords->sourceunit->matchStartAndRemove( $parts[0] ) ) {
- if( $magicWords->targetunit->matchStartAndRemove( $parts[1] ) ){
- $this->targetUnit =& $this->sourceUnit;
- } else {
- $this->sourceUnit = new ConvertUnit( $parts[1] );
- }
-
- } elseif ( $magicWords->targetunit->matchStartAndRemove( $parts[0] ) ) {
- if( $magicWords->sourceunit->matchStartAndRemove( $parts[1] ) ){
- $this->targetUnit =& $this->sourceUnit;
- } else {
- $this->targetUnit = new ConvertUnit( $parts[1] );
- }
-
- } elseif( $magicWords->decimalplaces->matchStartAndRemove( $parts[0] ) ) {
- $this->decimalPlaces = intval( $parts[1] );
-
- } elseif( $magicWords->significantfigures->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( $magicWords->language->matchStartAndRemove( $parts[0] ) ) {
- # if this is an invalid code we'll get $wgContLang back
- $this->language = Language::factory( $parts[1] );
- }
-
- } elseif( $magicWords->linkunit->matchStartAndRemove( $parts[0] ) ) {
- $this->link = true;
-
- } elseif( $magicWords->abbreviate->matchStartAndRemove( $parts[0] ) ) {
- $this->abbreviate = true;
-
- } elseif( $magicWords->rawsuffix->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 );
- } else {
- # The string has no unit on the end, so it's been trimmed to the end of the
- # last digit, meaning the unit specified by #sourceunit won't have any space
- $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->getLocalisedName(true),
- $this->targetUnit->dimension->getLocalisedName(true)
- );
- }
-
- # If the Language hasn't been deliberately specified, get it from the wiki's
- # content language, but run it through a configurable map first
- if( $this->language === true ){
- global $wgContLang, $wgPFUnitLanguageVariants;
- $code = $wgContLang->getCode();
- if( isset( $wgPFUnitLanguageVariants[$code] ) ){
- $this->language = Language::factory( $wgPFUnitLanguageVariants[$code] );
- } else {
- # Ok, actually *do* use $wgContLang
- $this->language = true;
- }
- }
-
- return $this->processString( $string );
- }
-
- /**
- * Find the unit at the end of the string and load $this->sourceUnit with an appropriate
- * ConvertUnit, or throw an exception if the unit is unrecognised.
- * @param $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
- * @return String
- */
- protected function processString( $string ){
- # Replace values
- $string = preg_replace_callback(
- self::NUM_REGEX,
- array( $this, 'convert' ),
- ltrim( preg_replace( self::UNITS_REGEX, '$1', $string ) )
- );
- if( $this->raw ){
- return trim( $string );
- } else {
- return $this->targetUnit->getText(
- $string,
- $this->lastValue,
- $this->link,
- $this->abbreviate,
- $this->language
- );
- }
- }
-
- /**
- * Express a value in the $sourceUnit in terms of the $targetUnit, preserving
- * an appropriate degree of accuracy.
- * @param $value String
- * @return String
- */
- 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;
-
- /**
- * Constructor
- * @param $var ConvertDimension|Int a dimension constant or existing unit
- * @param $var2 ConvertDimension|Int optionally another dimension constant for a compound unit $var/$var2
- */
- 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[$dim],
- self::$legalDimensions[$dim2],
- );
- } 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
- * @param $expandCompound Bool Whether to return a string instead of an array of strings in
- * case of a compound unit
- * @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,
- * TAKES_SI_PREFIXES,
- * )
- * )
- * )
- */
- protected static $units = array(
- ConvertDimension::DIM_LENGTH => array(
- 'metre' => array( 1, 'm', true ),
- 'angstrom' => array( 0.00000001, '\x{00C5}', false ),
-
- 'mile' => array( 1609.344, 'mi|miles?', false ),
- 'furlong' => array( 201.168, 'furlong', false ),
- 'chain' => array( 20.1168 , 'chain', false ),
- 'rod' => array( 5.0292, 'rod|pole|perch', false ),
- 'fathom' => array( 1.8288, 'fathom', false ),
- 'yard' => array( 0.9144, 'yards?|yd', false ),
- 'foot' => array( 0.3048, 'foot|feet|ft', false ),
- 'hand' => array( 0.1016, 'hands?', false ),
- 'inch' => array( 0.0254, 'inch|inches|in', false ),
-
- 'nauticalmile' => array( 1852, 'nauticalmiles?|nmi', false ),
- 'nauticalmileuk' => array( 1853.184, 'old[Uu][Kk]nmi|[Bb]rnmi|admi', false ),
- 'nauticalmileus' => array( 1853.24496, 'old[Uu][Ss]nmi', false ),
-
- 'parsec' => array( 3.0856775813057E16, 'parsecs?|pc', true ),
- 'lightyear' => array( 9.4607304725808E15, 'lightyears?|ly', true ),
- 'astronomicalunit' => array( 149597870700, 'astronomicalunits?|AU|au', false ),
- ),
-
- ConvertDimension::DIM_AREA => array(
- 'squarekilometre' => array( 1E6, 'km2|km\^2', false ),
- 'squaremetre' => array( 1, 'm2|m\^2', false ),
- 'squarecentimetre' => array( 1E-4, 'cm2|cm\^2', false ),
- 'squaremillimetre' => array( 1E-6, 'mm2|mm\^2', false ),
- 'hectare' => array( 1E4, 'hectares?|ha', false ),
-
- 'squaremile' => array( 2589988.110336, 'sqmi|mi2|mi\^2', false ),
- 'acre' => array( 4046.856422, 'acres?', false ),
- 'squareyard' => array( 0.83612736, 'sqyd|yd2|yd\^2', false ),
- 'squarefoot' => array( 0.09290304, 'sqft|ft2|ft\^2', false ),
- 'squareinch' => array( 0.00064516, 'sqin|in2|in\^2', false ),
-
- 'squarenauticalmile' => array( 3429904, 'sqnmi|nmi2|nmi\^2', false ),
- 'dunam' => array( 1000, 'dunam', false ),
- 'tsubo' => array( 3.305785, 'tsubo', false ),
- ),
-
- ConvertDimension::DIM_VOLUME => array(
- 'cubicmetre' => array( 1, 'm3|m\^3', false ),
- 'cubiccentimetre' => array( 1E-6, 'cm3|cm\^3', false ),
- 'cubicmillimetre' => array( 1E-9, 'mm3|mm\^3', false ),
- 'litre' => array( 1E-3 , 'l', true ),
-
- 'cubicyard' => array( 0.764554857984, 'cuyd|yd3|yd\^3', false ),
- 'cubicfoot' => array( 0.028316846592, 'cuft|ft3|ft\^3', false ),
- 'cubicinch' => array( 0.000016387064, 'cuin|in3|in\^3', false ),
- 'barrel' => array( 0.16365924, 'bbl|barrels?|impbbl', false ),
- 'bushel' => array( 0.03636872, 'bsh|bushels?|impbsh', false ),
- 'gallon' => array( 0.00454609, 'gal|gallons?|impgal', false ),
- 'quart' => array( 0.0011365225, 'qt|quarts?|impqt', false ),
- 'pint' => array( 0.00056826125, 'pt|pints?|imppt', false ),
- 'fluidounce' => array( 0.0000284130625, 'floz|impfloz', false ),
-
- 'barrelus' => array( 0.119240471196, 'usbbl', false ),
- 'barreloil' => array( 0.158987294928, 'oilbbl', false ),
- 'barrelbeer' => array( 0.117347765304, 'beerbbl', false ),
- 'usgallon' => array( 0.003785411784, 'usgal', false ),
- 'usquart' => array( 0.000946352946, 'usqt', false ),
- 'uspint' => array( 0.000473176473, 'uspt', false ),
- 'usfluidounce' => array( 0.0000295735295625, 'usfloz', false ),
- 'usdrybarrel' => array( 0.11562819898508, 'usdrybbl', false ),
- 'usbushel' => array( 0.03523907016688, 'usbsh', false ),
- 'usdrygallon' => array( 0.00440488377086, 'usdrygal', false ),
- 'usdryquart' => array( 0.001101220942715, 'usdryqt', false ),
- 'usdrypint' => array( 0.0005506104713575, 'usdrypt', false ),
- ),
-
- ConvertDimension::DIM_TIME => array(
- 'year' => array( 31557600, 'yr', true ),
- 'day' => array( 86400, 'd|days?', false ),
- 'hour' => array( 3600, 'hours?|hr|h', false ),
- 'minute' => array( 60, 'minutes?|mins?', false ),
- 'second' => array( 1, 's', false ),
- ),
-
- ConvertDimension::DIM_SPEED => array(
- 'knot' => array( 0.514444444, 'knot|kn', false ),
- 'speedoflight' => array( 2.9979E8, 'c', false ),
- ),
-
- ConvertDimension::DIM_PRESSURE => array(
- 'pascal' => array( 1, 'Pa', true ),
-
- 'bar' => array( 100000, 'bar', false ),
- 'decibar' => array( 10000, 'dbar', false ),
- 'millibar' => array( 100 , 'mbar|mb', false ),
- 'kilobarye' => array( 100, 'kba', false ),
- 'barye' => array( 0.1, 'ba', false ),
-
- 'atmosphere' => array( 101325, 'atm|atmospheres?', false ),
- 'torr' => array( 133.32237, 'torr', false ),
- 'mmhg' => array( 133.322387415, 'mmHg', false ),
- 'inhg' => array( 3386.38864034, 'inHg', false ),
- 'psi' => array( 6894.757293, 'psi', false ),
- ),
- # TODO: other dimensions as needed
- );
-
- /**
- * array(
- * PREFIX => array(
- * CONVERSION,
- * REGEX,
- * )
- * )
- * They're out of order because this is the order in which they are tested, and
- * some prefixes are much more likely to occur than others
- */
- protected static $prefixes = array(
- 'kilo' => array( 1E3, 'k' ),
- 'milli' => array( 1E-3, 'm' ),
- 'centi' => array( 1E-2, 'c' ),
- 'giga' => array( 1E9, 'G' ),
- 'micro' => array( 1E-6, '(?:\x{03BC}|\x{00B5})' ), # There are two similar mu characters
- 'mega' => array( 1E6, 'M' ),
- 'nano' => array( 1E-9, 'n' ),
- 'hecto' => array( 1E2, 'h' ),
- 'deca' => array( 1E1, 'da' ),
- 'deci' => array( 1E-1, 'd' ),
- 'yotta' => array( 1E24, 'Y' ),
- 'zetta' => array( 1E21, 'Z' ),
- 'exa' => array( 1E18, 'E' ),
- 'peta' => array( 1E15, 'P' ),
- 'tera' => array( 1E12, 'T' ),
- 'pico' => array( 1E-12, 'p' ),
- 'femto' => array( 1E-15, 'f' ),
- 'atto' => array( 1E-18, 'a' ),
- 'zepto' => array( 1E-21, 'z' ),
- 'yocto' => array( 1E-24, 'y' ),
- );
-
- # 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$/u' => '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;
-
- # The SI prefix, if applicable
- protected $prefix = null;
-
- /***************** 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 );
- }
-
- /**
- * Parse a raw unit string, and populate member variables
- * @param $rawUnit String
- */
- 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
- || ( !$data[2] && preg_match( "/^({$data[1]})$/u", $parts[0] ) )
- || ( $data[2] && preg_match( "/^(" . ConvertParser::PREFIX_REGEX . ")(" . $data[1] . ")$/u", $parts[0] ) ) )
- {
- $this->dimension = new ConvertDimension( self::$dimensionMap[$unit] );
- $this->conversion = $data[0];
- $this->regex = $data[1];
- $this->unitName = $unit;
-
- # Grab the SI prefix, if it's allowed and there is one
- if( $data[2] && !preg_match( "/^({$data[1]})$/u", $parts[0] ) ){
- foreach( self::$prefixes as $prefix => $pdata ){
- if( preg_match( "/^({$pdata[1]})({$data[1]})$/u", $parts[0] ) ){
- $this->prefix = $prefix;
- break;
- }
- }
- }
-
- 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 );
- $this->prefix = array( $top->prefix, $bottom->prefix );
- return;
-
- } else {
- # Whaaat? Too many parts
- throw new ConvertError( 'doublecompoundunit', $rawUnit );
- }
- }
-
- /**
- * Get the mathematical factor which will convert a measurement in this unit into a
- * measurement in the SI base unit for the dimension
- * @return double
- */
- public function getConversion(){
- return $this->conversion * $this->getPrefixConversion();
- }
-
- /**
- * Get the conversion factor associated with the prefix(es) in the unit
- * @return double
- */
- public function getPrefixConversion(){
- if( !$this->prefix ){
- return 1;
- } elseif( is_array( $this->prefix ) ){
- $x = $this->prefix[0] !== null
- ? self::$prefixes[$this->prefix[0]][0]
- : 1;
- if( $this->prefix[1] !== null ){
- $x *= self::$prefixes[$this->prefix[1]][0];
- }
- return $x;
- } else {
- return self::$prefixes[$this->prefix][0];
- }
- }
-
- /**
- * Get a regular expression which will match keywords for this unit
- * @return String
- */
- public function getRegex(){
- return $this->regex;
- }
-
- /**
- * Get the text of the unit
- * @param $string String Original text, with the number converted
- * @param $value String number for PLURAL support
- * @param $link Bool
- * @param $abbreviate Bool
- * @param $language Language
- * @return String
- */
- public function getText( $string, $value, $link=false, $abbreviate=false, $language=null ){
- global $wgContLang;
- $value = $wgContLang->formatNum( $value );
-
- if( !is_array( $this->unitName ) ){
- $msgText = $this->getTextFromMessage(
- $this->dimension->getName(),
- $this->unitName,
- $this->prefix,
- $string, $value, $link, $abbreviate, $language
- );
-
- } 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>"
- $msgText = $this->getTextFromMessage(
- $this->dimension->getName(true),
- "{$this->unitName[0]}-{$this->unitName[1]}",
- $this->prefix, # This will probably be rubbish, but it's the wiki users' problem, not ours
- $string, $value, $link, $abbreviate, $language
- );
-
- } else {
- $dimensionNames = $this->dimension->getName();
- $msgText = $this->getTextFromMessage(
- $dimensionNames[0],
- $this->unitName[0],
- $this->prefix[0],
- $string, $value, $link, $abbreviate, $language
- );
- $msg2Text = $this->getTextFromMessage(
- $dimensionNames[1],
- $this->unitName[1],
- $this->prefix[1],
- '',
- 1, # Singular for denominator
- $link, $abbreviate, $language
- );
- $msgText = "$msgText/$msg2Text";
- }
-
- return trim( $msgText );
- }
-
- /**
- * Retrieve the unit text from actual messages
- * @param $dimension String
- * @param $unit String
- * @param $prefix String
- * @param $string String
- * @param $number String the actual value (for {{PLURAL}} etc)
- * @param $link Bool
- * @param $abbreviate Bool
- * @param $language Language|Bool|null
- * @return String
- */
- protected function getTextFromMessage( $dimension, $unit, $prefix, $string, $number, $link, $abbreviate, $language ){
- $abbr = $abbreviate ? '-abbr' : '';
- $prefix = $prefix === null
- ? ''
- : wfMsgExt( "pfunc-convert-prefix-$prefix$abbr", array( 'parsemag', 'language' => $language ) );
-
- $text = wfMsgExt(
- "pfunc-convert-unit-$dimension-$unit$abbr",
- array( 'parsemag', 'language' => $language ),
- $string,
- $number,
- $prefix
- );
-
- if( $link && !wfEmptyMsg( "pfunc-convert-unit-$dimension-$unit-link" ) ){
- $title = Title::newFromText(
- wfMsgForContentNoTrans( "pfunc-convert-unit-$dimension-$unit-link" ),
- $prefix
- );
- if( $title instanceof Title ){
- $text = "[[{$title->getFullText()}|$text]]";
- }
- }
-
- return $text;
- }
-
- /**
- * Get the default (usually SI) unit associated with this particular dimension
- * @return ConvertUnit
- */
- public function getDefaultUnit(){
- return new ConvertUnit( self::$defaultUnit[$this->dimension->value] );
- }
-}