|
|
|
|
/**
|
|
|
|
|
* Globalize v1.0.0
|
|
|
|
|
*
|
|
|
|
|
* http://github.com/jquery/globalize
|
|
|
|
|
*
|
|
|
|
|
* Copyright jQuery Foundation and other contributors
|
|
|
|
|
* Released under the MIT license
|
|
|
|
|
* http://jquery.org/license
|
|
|
|
|
*
|
|
|
|
|
* Date: 2015-04-23T12:02Z
|
|
|
|
|
*/
|
|
|
|
|
/*!
|
|
|
|
|
* Globalize v1.0.0 2015-04-23T12:02Z Released under the MIT license
|
|
|
|
|
* http://git.io/TrdQbw
|
|
|
|
|
*/
|
|
|
|
|
(function( root, factory ) {
|
|
|
|
|
|
|
|
|
|
// UMD returnExports
|
|
|
|
|
if ( typeof define === "function" && define.amd ) {
|
|
|
|
|
|
|
|
|
|
// AMD
|
|
|
|
|
define([
|
|
|
|
|
"cldr",
|
|
|
|
|
"../globalize",
|
|
|
|
|
"cldr/event",
|
|
|
|
|
"cldr/supplemental"
|
|
|
|
|
], factory );
|
|
|
|
|
} else if ( typeof exports === "object" ) {
|
|
|
|
|
|
|
|
|
|
// Node, CommonJS
|
|
|
|
|
module.exports = factory( require( "cldrjs" ), require( "globalize" ) );
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
|
|
// Global
|
|
|
|
|
factory( root.Cldr, root.Globalize );
|
|
|
|
|
}
|
|
|
|
|
}(this, function( Cldr, Globalize ) {
|
|
|
|
|
|
|
|
|
|
var createError = Globalize._createError,
|
|
|
|
|
objectExtend = Globalize._objectExtend,
|
|
|
|
|
regexpEscape = Globalize._regexpEscape,
|
|
|
|
|
stringPad = Globalize._stringPad,
|
|
|
|
|
validateCldr = Globalize._validateCldr,
|
|
|
|
|
validateDefaultLocale = Globalize._validateDefaultLocale,
|
|
|
|
|
validateParameterPresence = Globalize._validateParameterPresence,
|
|
|
|
|
validateParameterRange = Globalize._validateParameterRange,
|
|
|
|
|
validateParameterType = Globalize._validateParameterType,
|
|
|
|
|
validateParameterTypePlainObject = Globalize._validateParameterTypePlainObject;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var createErrorUnsupportedFeature = function( feature ) {
|
|
|
|
|
return createError( "E_UNSUPPORTED", "Unsupported {feature}.", {
|
|
|
|
|
feature: feature
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var validateParameterTypeNumber = function( value, name ) {
|
|
|
|
|
validateParameterType(
|
|
|
|
|
value,
|
|
|
|
|
name,
|
|
|
|
|
value === undefined || typeof value === "number",
|
|
|
|
|
"Number"
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var validateParameterTypeString = function( value, name ) {
|
|
|
|
|
validateParameterType(
|
|
|
|
|
value,
|
|
|
|
|
name,
|
|
|
|
|
value === undefined || typeof value === "string",
|
|
|
|
|
"a string"
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* goupingSeparator( number, primaryGroupingSize, secondaryGroupingSize )
|
|
|
|
|
*
|
|
|
|
|
* @number [Number].
|
|
|
|
|
*
|
|
|
|
|
* @primaryGroupingSize [Number]
|
|
|
|
|
*
|
|
|
|
|
* @secondaryGroupingSize [Number]
|
|
|
|
|
*
|
|
|
|
|
* Return the formatted number with group separator.
|
|
|
|
|
*/
|
|
|
|
|
var numberFormatGroupingSeparator = function( number, primaryGroupingSize, secondaryGroupingSize ) {
|
|
|
|
|
var index,
|
|
|
|
|
currentGroupingSize = primaryGroupingSize,
|
|
|
|
|
ret = "",
|
|
|
|
|
sep = ",",
|
|
|
|
|
switchToSecondary = secondaryGroupingSize ? true : false;
|
|
|
|
|
|
|
|
|
|
number = String( number ).split( "." );
|
|
|
|
|
index = number[ 0 ].length;
|
|
|
|
|
|
|
|
|
|
while ( index > currentGroupingSize ) {
|
|
|
|
|
ret = number[ 0 ].slice( index - currentGroupingSize, index ) +
|
|
|
|
|
( ret.length ? sep : "" ) + ret;
|
|
|
|
|
index -= currentGroupingSize;
|
|
|
|
|
if ( switchToSecondary ) {
|
|
|
|
|
currentGroupingSize = secondaryGroupingSize;
|
|
|
|
|
switchToSecondary = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
number[ 0 ] = number[ 0 ].slice( 0, index ) + ( ret.length ? sep : "" ) + ret;
|
|
|
|
|
return number.join( "." );
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* integerFractionDigits( number, minimumIntegerDigits, minimumFractionDigits,
|
|
|
|
|
* maximumFractionDigits, round, roundIncrement )
|
|
|
|
|
*
|
|
|
|
|
* @number [Number]
|
|
|
|
|
*
|
|
|
|
|
* @minimumIntegerDigits [Number]
|
|
|
|
|
*
|
|
|
|
|
* @minimumFractionDigits [Number]
|
|
|
|
|
*
|
|
|
|
|
* @maximumFractionDigits [Number]
|
|
|
|
|
*
|
|
|
|
|
* @round [Function]
|
|
|
|
|
*
|
|
|
|
|
* @roundIncrement [Function]
|
|
|
|
|
*
|
|
|
|
|
* Return the formatted integer and fraction digits.
|
|
|
|
|
*/
|
|
|
|
|
var numberFormatIntegerFractionDigits = function( number, minimumIntegerDigits, minimumFractionDigits, maximumFractionDigits, round,
|
|
|
|
|
roundIncrement ) {
|
|
|
|
|
|
|
|
|
|
// Fraction
|
|
|
|
|
if ( maximumFractionDigits ) {
|
|
|
|
|
|
|
|
|
|
// Rounding
|
|
|
|
|
if ( roundIncrement ) {
|
|
|
|
|
number = round( number, roundIncrement );
|
|
|
|
|
|
|
|
|
|
// Maximum fraction digits
|
|
|
|
|
} else {
|
|
|
|
|
number = round( number, { exponent: -maximumFractionDigits } );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Minimum fraction digits
|
|
|
|
|
if ( minimumFractionDigits ) {
|
|
|
|
|
number = String( number ).split( "." );
|
|
|
|
|
number[ 1 ] = stringPad( number[ 1 ] || "", minimumFractionDigits, true );
|
|
|
|
|
number = number.join( "." );
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
number = round( number );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
number = String( number );
|
|
|
|
|
|
|
|
|
|
// Minimum integer digits
|
|
|
|
|
if ( minimumIntegerDigits ) {
|
|
|
|
|
number = number.split( "." );
|
|
|
|
|
number[ 0 ] = stringPad( number[ 0 ], minimumIntegerDigits );
|
|
|
|
|
number = number.join( "." );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return number;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* toPrecision( number, precision, round )
|
|
|
|
|
*
|
|
|
|
|
* @number (Number)
|
|
|
|
|
*
|
|
|
|
|
* @precision (Number) significant figures precision (not decimal precision).
|
|
|
|
|
*
|
|
|
|
|
* @round (Function)
|
|
|
|
|
*
|
|
|
|
|
* Return number.toPrecision( precision ) using the given round function.
|
|
|
|
|
*/
|
|
|
|
|
var numberToPrecision = function( number, precision, round ) {
|
|
|
|
|
var roundOrder;
|
|
|
|
|
|
|
|
|
|
// Get number at two extra significant figure precision.
|
|
|
|
|
number = number.toPrecision( precision + 2 );
|
|
|
|
|
|
|
|
|
|
// Then, round it to the required significant figure precision.
|
|
|
|
|
roundOrder = Math.ceil( Math.log( Math.abs( number ) ) / Math.log( 10 ) );
|
|
|
|
|
roundOrder -= precision;
|
|
|
|
|
|
|
|
|
|
return round( number, { exponent: roundOrder } );
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* toPrecision( number, minimumSignificantDigits, maximumSignificantDigits, round )
|
|
|
|
|
*
|
|
|
|
|
* @number [Number]
|
|
|
|
|
*
|
|
|
|
|
* @minimumSignificantDigits [Number]
|
|
|
|
|
*
|
|
|
|
|
* @maximumSignificantDigits [Number]
|
|
|
|
|
*
|
|
|
|
|
* @round [Function]
|
|
|
|
|
*
|
|
|
|
|
* Return the formatted significant digits number.
|
|
|
|
|
*/
|
|
|
|
|
var numberFormatSignificantDigits = function( number, minimumSignificantDigits, maximumSignificantDigits, round ) {
|
|
|
|
|
var atMinimum, atMaximum;
|
|
|
|
|
|
|
|
|
|
// Sanity check.
|
|
|
|
|
if ( minimumSignificantDigits > maximumSignificantDigits ) {
|
|
|
|
|
maximumSignificantDigits = minimumSignificantDigits;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
atMinimum = numberToPrecision( number, minimumSignificantDigits, round );
|
|
|
|
|
atMaximum = numberToPrecision( number, maximumSignificantDigits, round );
|
|
|
|
|
|
|
|
|
|
// Use atMaximum only if it has more significant digits than atMinimum.
|
|
|
|
|
number = +atMinimum === +atMaximum ? atMinimum : atMaximum;
|
|
|
|
|
|
|
|
|
|
// Expand integer numbers, eg. 123e5 to 12300.
|
|
|
|
|
number = ( +number ).toString( 10 );
|
|
|
|
|
|
|
|
|
|
if ( (/e/).test( number ) ) {
|
|
|
|
|
throw createErrorUnsupportedFeature({
|
|
|
|
|
feature: "integers out of (1e21, 1e-7)"
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add trailing zeros if necessary.
|
|
|
|
|
if ( minimumSignificantDigits - number.replace( /^0+|\./g, "" ).length > 0 ) {
|
|
|
|
|
number = number.split( "." );
|
|
|
|
|
number[ 1 ] = stringPad( number[ 1 ] || "", minimumSignificantDigits - number[ 0 ].replace( /^0+/, "" ).length, true );
|
|
|
|
|
number = number.join( "." );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return number;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* format( number, properties )
|
|
|
|
|
*
|
|
|
|
|
* @number [Number].
|
|
|
|
|
*
|
|
|
|
|
* @properties [Object] Output of number/format-properties.
|
|
|
|
|
*
|
|
|
|
|
* Return the formatted number.
|
|
|
|
|
* ref: http://www.unicode.org/reports/tr35/tr35-numbers.html
|
|
|
|
|
*/
|
|
|
|
|
var numberFormat = function( number, properties ) {
|
|
|
|
|
var infinitySymbol, maximumFractionDigits, maximumSignificantDigits, minimumFractionDigits,
|
|
|
|
|
minimumIntegerDigits, minimumSignificantDigits, nanSymbol, nuDigitsMap, padding, prefix,
|
|
|
|
|
primaryGroupingSize, pattern, ret, round, roundIncrement, secondaryGroupingSize, suffix,
|
|
|
|
|
symbolMap;
|
|
|
|
|
|
|
|
|
|
padding = properties[ 1 ];
|
|
|
|
|
minimumIntegerDigits = properties[ 2 ];
|
|
|
|
|
minimumFractionDigits = properties[ 3 ];
|
|
|
|
|
maximumFractionDigits = properties[ 4 ];
|
|
|
|
|
minimumSignificantDigits = properties[ 5 ];
|
|
|
|
|
maximumSignificantDigits = properties[ 6 ];
|
|
|
|
|
roundIncrement = properties[ 7 ];
|
|
|
|
|
primaryGroupingSize = properties[ 8 ];
|
|
|
|
|
secondaryGroupingSize = properties[ 9 ];
|
|
|
|
|
round = properties[ 15 ];
|
|
|
|
|
infinitySymbol = properties[ 16 ];
|
|
|
|
|
nanSymbol = properties[ 17 ];
|
|
|
|
|
symbolMap = properties[ 18 ];
|
|
|
|
|
nuDigitsMap = properties[ 19 ];
|
|
|
|
|
|
|
|
|
|
// NaN
|
|
|
|
|
if ( isNaN( number ) ) {
|
|
|
|
|
return nanSymbol;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( number < 0 ) {
|
|
|
|
|
pattern = properties[ 12 ];
|
|
|
|
|
prefix = properties[ 13 ];
|
|
|
|
|
suffix = properties[ 14 ];
|
|
|
|
|
} else {
|
|
|
|
|
pattern = properties[ 11 ];
|
|
|
|
|
prefix = properties[ 0 ];
|
|
|
|
|
suffix = properties[ 10 ];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Infinity
|
|
|
|
|
if ( !isFinite( number ) ) {
|
|
|
|
|
return prefix + infinitySymbol + suffix;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ret = prefix;
|
|
|
|
|
|
|
|
|
|
// Percent
|
|
|
|
|
if ( pattern.indexOf( "%" ) !== -1 ) {
|
|
|
|
|
number *= 100;
|
|
|
|
|
|
|
|
|
|
// Per mille
|
|
|
|
|
} else if ( pattern.indexOf( "\u2030" ) !== -1 ) {
|
|
|
|
|
number *= 1000;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Significant digit format
|
|
|
|
|
if ( !isNaN( minimumSignificantDigits * maximumSignificantDigits ) ) {
|
|
|
|
|
number = numberFormatSignificantDigits( number, minimumSignificantDigits,
|
|
|
|
|
maximumSignificantDigits, round );
|
|
|
|
|
|
|
|
|
|
// Integer and fractional format
|
|
|
|
|
} else {
|
|
|
|
|
number = numberFormatIntegerFractionDigits( number, minimumIntegerDigits,
|
|
|
|
|
minimumFractionDigits, maximumFractionDigits, round, roundIncrement );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove the possible number minus sign
|
|
|
|
|
number = number.replace( /^-/, "" );
|
|
|
|
|
|
|
|
|
|
// Grouping separators
|
|
|
|
|
if ( primaryGroupingSize ) {
|
|
|
|
|
number = numberFormatGroupingSeparator( number, primaryGroupingSize,
|
|
|
|
|
secondaryGroupingSize );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ret += number;
|
|
|
|
|
|
|
|
|
|
// Scientific notation
|
|
|
|
|
// TODO implement here
|
|
|
|
|
|
|
|
|
|
// Padding/'([^']|'')+'|''|[.,\-+E%\u2030]/g
|
|
|
|
|
// TODO implement here
|
|
|
|
|
|
|
|
|
|
ret += suffix;
|
|
|
|
|
|
|
|
|
|
return ret.replace( /('([^']|'')+'|'')|./g, function( character, literal ) {
|
|
|
|
|
|
|
|
|
|
// Literals
|
|
|
|
|
if ( literal ) {
|
|
|
|
|
literal = literal.replace( /''/, "'" );
|
|
|
|
|
if ( literal.length > 2 ) {
|
|
|
|
|
literal = literal.slice( 1, -1 );
|
|
|
|
|
}
|
|
|
|
|
return literal;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Symbols
|
|
|
|
|
character = character.replace( /[.,\-+E%\u2030]/, function( symbol ) {
|
|
|
|
|
return symbolMap[ symbol ];
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Numbering system
|
|
|
|
|
if ( nuDigitsMap ) {
|
|
|
|
|
character = character.replace( /[0-9]/, function( digit ) {
|
|
|
|
|
return nuDigitsMap[ +digit ];
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return character;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* NumberingSystem( cldr )
|
|
|
|
|
*
|
|
|
|
|
* - http://www.unicode.org/reports/tr35/tr35-numbers.html#otherNumberingSystems
|
|
|
|
|
* - http://cldr.unicode.org/index/bcp47-extension
|
|
|
|
|
* - http://www.unicode.org/reports/tr35/#u_Extension
|
|
|
|
|
*/
|
|
|
|
|
var numberNumberingSystem = function( cldr ) {
|
|
|
|
|
var nu = cldr.attributes[ "u-nu" ];
|
|
|
|
|
|
|
|
|
|
if ( nu ) {
|
|
|
|
|
if ( nu === "traditio" ) {
|
|
|
|
|
nu = "traditional";
|
|
|
|
|
}
|
|
|
|
|
if ( [ "native", "traditional", "finance" ].indexOf( nu ) !== -1 ) {
|
|
|
|
|
|
|
|
|
|
// Unicode locale extension `u-nu` is set using either (native, traditional or
|
|
|
|
|
// finance). So, lookup the respective locale's numberingSystem and return it.
|
|
|
|
|
return cldr.main([ "numbers/otherNumberingSystems", nu ]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Unicode locale extension `u-nu` is set with an explicit numberingSystem. Return it.
|
|
|
|
|
return nu;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Return the default numberingSystem.
|
|
|
|
|
return cldr.main( "numbers/defaultNumberingSystem" );
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* nuMap( cldr )
|
|
|
|
|
*
|
|
|
|
|
* @cldr [Cldr instance].
|
|
|
|
|
*
|
|
|
|
|
* Return digits map if numbering system is different than `latn`.
|
|
|
|
|
*/
|
|
|
|
|
var numberNumberingSystemDigitsMap = function( cldr ) {
|
|
|
|
|
var aux,
|
|
|
|
|
nu = numberNumberingSystem( cldr );
|
|
|
|
|
|
|
|
|
|
if ( nu === "latn" ) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
aux = cldr.supplemental([ "numberingSystems", nu ]);
|
|
|
|
|
|
|
|
|
|
if ( aux._type !== "numeric" ) {
|
|
|
|
|
throw createErrorUnsupportedFeature( "`" + aux._type + "` numbering system" );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return aux._digits;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* EBNF representation:
|
|
|
|
|
*
|
|
|
|
|
* number_pattern_re = prefix?
|
|
|
|
|
* padding?
|
|
|
|
|
* (integer_fraction_pattern | significant_pattern)
|
|
|
|
|
* scientific_notation?
|
|
|
|
|
* suffix?
|
|
|
|
|
*
|
|
|
|
|
* prefix = non_number_stuff
|
|
|
|
|
*
|
|
|
|
|
* padding = "*" regexp(.)
|
|
|
|
|
*
|
|
|
|
|
* integer_fraction_pattern = integer_pattern
|
|
|
|
|
* fraction_pattern?
|
|
|
|
|
*
|
|
|
|
|
* integer_pattern = regexp([#,]*[0,]*0+)
|
|
|
|
|
*
|
|
|
|
|
* fraction_pattern = "." regexp(0*[0-9]*#*)
|
|
|
|
|
*
|
|
|
|
|
* significant_pattern = regexp([#,]*@+#*)
|
|
|
|
|
*
|
|
|
|
|
* scientific_notation = regexp(E\+?0+)
|
|
|
|
|
*
|
|
|
|
|
* suffix = non_number_stuff
|
|
|
|
|
*
|
|
|
|
|
* non_number_stuff = regexp(('[^']+'|''|[^*#@0,.E])*)
|
|
|
|
|
*
|
|
|
|
|
*
|
|
|
|
|
* Regexp groups:
|
|
|
|
|
*
|
|
|
|
|
* 0: number_pattern_re
|
|
|
|
|
* 1: prefix
|
|
|
|
|
* 2: -
|
|
|
|
|
* 3: padding
|
|
|
|
|
* 4: (integer_fraction_pattern | significant_pattern)
|
|
|
|
|
* 5: integer_fraction_pattern
|
|
|
|
|
* 6: integer_pattern
|
|
|
|
|
* 7: fraction_pattern
|
|
|
|
|
* 8: significant_pattern
|
|
|
|
|
* 9: scientific_notation
|
|
|
|
|
* 10: suffix
|
|
|
|
|
* 11: -
|
|
|
|
|
*/
|
|
|
|
|
var numberPatternRe = (/^(('[^']+'|''|[^*#@0,.E])*)(\*.)?((([#,]*[0,]*0+)(\.0*[0-9]*#*)?)|([#,]*@+#*))(E\+?0+)?(('[^']+'|''|[^*#@0,.E])*)$/);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* format( number, pattern )
|
|
|
|
|
*
|
|
|
|
|
* @number [Number].
|
|
|
|
|
*
|
|
|
|
|
* @pattern [String] raw pattern for numbers.
|
|
|
|
|
*
|
|
|
|
|
* Return the formatted number.
|
|
|
|
|
* ref: http://www.unicode.org/reports/tr35/tr35-numbers.html
|
|
|
|
|
*/
|
|
|
|
|
var numberPatternProperties = function( pattern ) {
|
|
|
|
|
var aux1, aux2, fractionPattern, integerFractionOrSignificantPattern, integerPattern,
|
|
|
|
|
maximumFractionDigits, maximumSignificantDigits, minimumFractionDigits,
|
|
|
|
|
minimumIntegerDigits, minimumSignificantDigits, padding, prefix, primaryGroupingSize,
|
|
|
|
|
roundIncrement, scientificNotation, secondaryGroupingSize, significantPattern, suffix;
|
|
|
|
|
|
|
|
|
|
pattern = pattern.match( numberPatternRe );
|
|
|
|
|
if ( !pattern ) {
|
|
|
|
|
throw new Error( "Invalid pattern: " + pattern );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
prefix = pattern[ 1 ];
|
|
|
|
|
padding = pattern[ 3 ];
|
|
|
|
|
integerFractionOrSignificantPattern = pattern[ 4 ];
|
|
|
|
|
significantPattern = pattern[ 8 ];
|
|
|
|
|
scientificNotation = pattern[ 9 ];
|
|
|
|
|
suffix = pattern[ 10 ];
|
|
|
|
|
|
|
|
|
|
// Significant digit format
|
|
|
|
|
if ( significantPattern ) {
|
|
|
|
|
significantPattern.replace( /(@+)(#*)/, function( match, minimumSignificantDigitsMatch, maximumSignificantDigitsMatch ) {
|
|
|
|
|
minimumSignificantDigits = minimumSignificantDigitsMatch.length;
|
|
|
|
|
maximumSignificantDigits = minimumSignificantDigits +
|
|
|
|
|
maximumSignificantDigitsMatch.length;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Integer and fractional format
|
|
|
|
|
} else {
|
|
|
|
|
fractionPattern = pattern[ 7 ];
|
|
|
|
|
integerPattern = pattern[ 6 ];
|
|
|
|
|
|
|
|
|
|
if ( fractionPattern ) {
|
|
|
|
|
|
|
|
|
|
// Minimum fraction digits, and rounding.
|
|
|
|
|
fractionPattern.replace( /[0-9]+/, function( match ) {
|
|
|
|
|
minimumFractionDigits = match;
|
|
|
|
|
});
|
|
|
|
|
if ( minimumFractionDigits ) {
|
|
|
|
|
roundIncrement = +( "0." + minimumFractionDigits );
|
|
|
|
|
minimumFractionDigits = minimumFractionDigits.length;
|
|
|
|
|
} else {
|
|
|
|
|
minimumFractionDigits = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Maximum fraction digits
|
|
|
|
|
// 1: ignore decimal character
|
|
|
|
|
maximumFractionDigits = fractionPattern.length - 1 /* 1 */;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Minimum integer digits
|
|
|
|
|
integerPattern.replace( /0+$/, function( match ) {
|
|
|
|
|
minimumIntegerDigits = match.length;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Scientific notation
|
|
|
|
|
if ( scientificNotation ) {
|
|
|
|
|
throw createErrorUnsupportedFeature({
|
|
|
|
|
feature: "scientific notation (not implemented)"
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Padding
|
|
|
|
|
if ( padding ) {
|
|
|
|
|
throw createErrorUnsupportedFeature({
|
|
|
|
|
feature: "padding (not implemented)"
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Grouping
|
|
|
|
|
if ( ( aux1 = integerFractionOrSignificantPattern.lastIndexOf( "," ) ) !== -1 ) {
|
|
|
|
|
|
|
|
|
|
// Primary grouping size is the interval between the last group separator and the end of
|
|
|
|
|
// the integer (or the end of the significant pattern).
|
|
|
|
|
aux2 = integerFractionOrSignificantPattern.split( "." )[ 0 ];
|
|
|
|
|
primaryGroupingSize = aux2.length - aux1 - 1;
|
|
|
|
|
|
|
|
|
|
// Secondary grouping size is the interval between the last two group separators.
|
|
|
|
|
if ( ( aux2 = integerFractionOrSignificantPattern.lastIndexOf( ",", aux1 - 1 ) ) !== -1 ) {
|
|
|
|
|
secondaryGroupingSize = aux1 - 1 - aux2;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Return:
|
|
|
|
|
// 0: @prefix String
|
|
|
|
|
// 1: @padding Array [ <character>, <count> ] TODO
|
|
|
|
|
// 2: @minimumIntegerDigits non-negative integer Number value indicating the minimum integer
|
|
|
|
|
// digits to be used. Numbers will be padded with leading zeroes if necessary.
|
|
|
|
|
// 3: @minimumFractionDigits and
|
|
|
|
|
// 4: @maximumFractionDigits are non-negative integer Number values indicating the minimum and
|
|
|
|
|
// maximum fraction digits to be used. Numbers will be rounded or padded with trailing
|
|
|
|
|
// zeroes if necessary.
|
|
|
|
|
// 5: @minimumSignificantDigits and
|
|
|
|
|
// 6: @maximumSignificantDigits are positive integer Number values indicating the minimum and
|
|
|
|
|
// maximum fraction digits to be shown. Either none or both of these properties are
|
|
|
|
|
// present; if they are, they override minimum and maximum integer and fraction digits
|
|
|
|
|
// – the formatter uses however many integer and fraction digits are required to display
|
|
|
|
|
// the specified number of significant digits.
|
|
|
|
|
// 7: @roundIncrement Decimal round increment or null
|
|
|
|
|
// 8: @primaryGroupingSize
|
|
|
|
|
// 9: @secondaryGroupingSize
|
|
|
|
|
// 10: @suffix String
|
|
|
|
|
return [
|
|
|
|
|
prefix,
|
|
|
|
|
padding,
|
|
|
|
|
minimumIntegerDigits,
|
|
|
|
|
minimumFractionDigits,
|
|
|
|
|
maximumFractionDigits,
|
|
|
|
|
minimumSignificantDigits,
|
|
|
|
|
maximumSignificantDigits,
|
|
|
|
|
roundIncrement,
|
|
|
|
|
primaryGroupingSize,
|
|
|
|
|
secondaryGroupingSize,
|
|
|
|
|
suffix
|
|
|
|
|
];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Symbol( name, cldr )
|
|
|
|
|
*
|
|
|
|
|
* @name [String] Symbol name.
|
|
|
|
|
*
|
|
|
|
|
* @cldr [Cldr instance].
|
|
|
|
|
*
|
|
|
|
|
* Return the localized symbol given its name.
|
|
|
|
|
*/
|
|
|
|
|
var numberSymbol = function( name, cldr ) {
|
|
|
|
|
return cldr.main([
|
|
|
|
|
"numbers/symbols-numberSystem-" + numberNumberingSystem( cldr ),
|
|
|
|
|
name
|
|
|
|
|
]);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var numberSymbolName = {
|
|
|
|
|
".": "decimal",
|
|
|
|
|
",": "group",
|
|
|
|
|
"%": "percentSign",
|
|
|
|
|
"+": "plusSign",
|
|
|
|
|
"-": "minusSign",
|
|
|
|
|
"E": "exponential",
|
|
|
|
|
"\u2030": "perMille"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* symbolMap( cldr )
|
|
|
|
|
*
|
|
|
|
|
* @cldr [Cldr instance].
|
|
|
|
|
*
|
|
|
|
|
* Return the (localized symbol, pattern symbol) key value pair, eg. {
|
|
|
|
|
* ".": "٫",
|
|
|
|
|
* ",": "٬",
|
|
|
|
|
* "%": "٪",
|
|
|
|
|
* ...
|
|
|
|
|
* };
|
|
|
|
|
*/
|
|
|
|
|
var numberSymbolMap = function( cldr ) {
|
|
|
|
|
var symbol,
|
|
|
|
|
symbolMap = {};
|
|
|
|
|
|
|
|
|
|
for ( symbol in numberSymbolName ) {
|
|
|
|
|
symbolMap[ symbol ] = numberSymbol( numberSymbolName[ symbol ], cldr );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return symbolMap;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var numberTruncate = function( value ) {
|
|
|
|
|
if ( isNaN( value ) ) {
|
|
|
|
|
return NaN;
|
|
|
|
|
}
|
|
|
|
|
return Math[ value < 0 ? "ceil" : "floor" ]( value );
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* round( method )
|
|
|
|
|
*
|
|
|
|
|
* @method [String] with either "round", "ceil", "floor", or "truncate".
|
|
|
|
|
*
|
|
|
|
|
* Return function( value, incrementOrExp ):
|
|
|
|
|
*
|
|
|
|
|
* @value [Number] eg. 123.45.
|
|
|
|
|
*
|
|
|
|
|
* @incrementOrExp [Number] optional, eg. 0.1; or
|
|
|
|
|
* [Object] Either { increment: <value> } or { exponent: <value> }
|
|
|
|
|
*
|
|
|
|
|
* Return the rounded number, eg:
|
|
|
|
|
* - round( "round" )( 123.45 ): 123;
|
|
|
|
|
* - round( "ceil" )( 123.45 ): 124;
|
|
|
|
|
* - round( "floor" )( 123.45 ): 123;
|
|
|
|
|
* - round( "truncate" )( 123.45 ): 123;
|
|
|
|
|
* - round( "round" )( 123.45, 0.1 ): 123.5;
|
|
|
|
|
* - round( "round" )( 123.45, 10 ): 120;
|
|
|
|
|
*
|
|
|
|
|
* Based on https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
|
|
|
|
|
* Ref: #376
|
|
|
|
|
*/
|
|
|
|
|
var numberRound = function( method ) {
|
|
|
|
|
method = method || "round";
|
|
|
|
|
method = method === "truncate" ? numberTruncate : Math[ method ];
|
|
|
|
|
|
|
|
|
|
return function( value, incrementOrExp ) {
|
|
|
|
|
var exp, increment;
|
|
|
|
|
|
|
|
|
|
value = +value;
|
|
|
|
|
|
|
|
|
|
// If the value is not a number, return NaN.
|
|
|
|
|
if ( isNaN( value ) ) {
|
|
|
|
|
return NaN;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Exponent given.
|
|
|
|
|
if ( typeof incrementOrExp === "object" && incrementOrExp.exponent ) {
|
|
|
|
|
exp = +incrementOrExp.exponent;
|
|
|
|
|
increment = 1;
|
|
|
|
|
|
|
|
|
|
if ( exp === 0 ) {
|
|
|
|
|
return method( value );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If the exp is not an integer, return NaN.
|
|
|
|
|
if ( !( typeof exp === "number" && exp % 1 === 0 ) ) {
|
|
|
|
|
return NaN;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Increment given.
|
|
|
|
|
} else {
|
|
|
|
|
increment = +incrementOrExp || 1;
|
|
|
|
|
|
|
|
|
|
if ( increment === 1 ) {
|
|
|
|
|
return method( value );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If the increment is not a number, return NaN.
|
|
|
|
|
if ( isNaN( increment ) ) {
|
|
|
|
|
return NaN;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
increment = increment.toExponential().split( "e" );
|
|
|
|
|
exp = +increment[ 1 ];
|
|
|
|
|
increment = +increment[ 0 ];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Shift & Round
|
|
|
|
|
value = value.toString().split( "e" );
|
|
|
|
|
value[ 0 ] = +value[ 0 ] / increment;
|
|
|
|
|
value[ 1 ] = value[ 1 ] ? ( +value[ 1 ] - exp ) : -exp;
|
|
|
|
|
value = method( +(value[ 0 ] + "e" + value[ 1 ] ) );
|
|
|
|
|
|
|
|
|
|
// Shift back
|
|
|
|
|
value = value.toString().split( "e" );
|
|
|
|
|
value[ 0 ] = +value[ 0 ] * increment;
|
|
|
|
|
value[ 1 ] = value[ 1 ] ? ( +value[ 1 ] + exp ) : exp;
|
|
|
|
|
return +( value[ 0 ] + "e" + value[ 1 ] );
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* formatProperties( pattern, cldr [, options] )
|
|
|
|
|
*
|
|
|
|
|
* @pattern [String] raw pattern for numbers.
|
|
|
|
|
*
|
|
|
|
|
* @cldr [Cldr instance].
|
|
|
|
|
*
|
|
|
|
|
* @options [Object]:
|
|
|
|
|
* - minimumIntegerDigits [Number]
|
|
|
|
|
* - minimumFractionDigits, maximumFractionDigits [Number]
|
|
|
|
|
* - minimumSignificantDigits, maximumSignificantDigits [Number]
|
|
|
|
|
* - round [String] "ceil", "floor", "round" (default), or "truncate".
|
|
|
|
|
* - useGrouping [Boolean] default true.
|
|
|
|
|
*
|
|
|
|
|
* Return the processed properties that will be used in number/format.
|
|
|
|
|
* ref: http://www.unicode.org/reports/tr35/tr35-numbers.html
|
|
|
|
|
*/
|
|
|
|
|
var numberFormatProperties = function( pattern, cldr, options ) {
|
|
|
|
|
var negativePattern, negativePrefix, negativeProperties, negativeSuffix, positivePattern,
|
|
|
|
|
properties;
|
|
|
|
|
|
|
|
|
|
function getOptions( attribute, propertyIndex ) {
|
|
|
|
|
if ( attribute in options ) {
|
|
|
|
|
properties[ propertyIndex ] = options[ attribute ];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
options = options || {};
|
|
|
|
|
pattern = pattern.split( ";" );
|
|
|
|
|
|
|
|
|
|
positivePattern = pattern[ 0 ];
|
|
|
|
|
|
|
|
|
|
negativePattern = pattern[ 1 ] || "-" + positivePattern;
|
|
|
|
|
negativeProperties = numberPatternProperties( negativePattern );
|
|
|
|
|
negativePrefix = negativeProperties[ 0 ];
|
|
|
|
|
negativeSuffix = negativeProperties[ 10 ];
|
|
|
|
|
|
|
|
|
|
properties = numberPatternProperties( positivePattern ).concat([
|
|
|
|
|
positivePattern,
|
|
|
|
|
negativePrefix + positivePattern + negativeSuffix,
|
|
|
|
|
negativePrefix,
|
|
|
|
|
negativeSuffix,
|
|
|
|
|
numberRound( options.round ),
|
|
|
|
|
numberSymbol( "infinity", cldr ),
|
|
|
|
|
numberSymbol( "nan", cldr ),
|
|
|
|
|
numberSymbolMap( cldr ),
|
|
|
|
|
numberNumberingSystemDigitsMap( cldr )
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
getOptions( "minimumIntegerDigits", 2 );
|
|
|
|
|
getOptions( "minimumFractionDigits", 3 );
|
|
|
|
|
getOptions( "maximumFractionDigits", 4 );
|
|
|
|
|
getOptions( "minimumSignificantDigits", 5 );
|
|
|
|
|
getOptions( "maximumSignificantDigits", 6 );
|
|
|
|
|
|
|
|
|
|
// Grouping separators
|
|
|
|
|
if ( options.useGrouping === false ) {
|
|
|
|
|
properties[ 8 ] = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Normalize number of digits if only one of either minimumFractionDigits or
|
|
|
|
|
// maximumFractionDigits is passed in as an option
|
|
|
|
|
if ( "minimumFractionDigits" in options && !( "maximumFractionDigits" in options ) ) {
|
|
|
|
|
// maximumFractionDigits = Math.max( minimumFractionDigits, maximumFractionDigits );
|
|
|
|
|
properties[ 4 ] = Math.max( properties[ 3 ], properties[ 4 ] );
|
|
|
|
|
} else if ( !( "minimumFractionDigits" in options ) &&
|
|
|
|
|
"maximumFractionDigits" in options ) {
|
|
|
|
|
// minimumFractionDigits = Math.min( minimumFractionDigits, maximumFractionDigits );
|
|
|
|
|
properties[ 3 ] = Math.min( properties[ 3 ], properties[ 4 ] );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Return:
|
|
|
|
|
// 0-10: see number/pattern-properties.
|
|
|
|
|
// 11: @positivePattern [String] Positive pattern.
|
|
|
|
|
// 12: @negativePattern [String] Negative pattern.
|
|
|
|
|
// 13: @negativePrefix [String] Negative prefix.
|
|
|
|
|
// 14: @negativeSuffix [String] Negative suffix.
|
|
|
|
|
// 15: @round [Function] Round function.
|
|
|
|
|
// 16: @infinitySymbol [String] Infinity symbol.
|
|
|
|
|
// 17: @nanSymbol [String] NaN symbol.
|
|
|
|
|
// 18: @symbolMap [Object] A bunch of other symbols.
|
|
|
|
|
// 19: @nuDigitsMap [Array] Digits map if numbering system is different than `latn`.
|
|
|
|
|
return properties;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* EBNF representation:
|
|
|
|
|
*
|
|
|
|
|
* number_pattern_re = prefix_including_padding?
|
|
|
|
|
* number
|
|
|
|
|
* scientific_notation?
|
|
|
|
|
* suffix?
|
|
|
|
|
*
|
|
|
|
|
* number = integer_including_group_separator fraction_including_decimal_separator
|
|
|
|
|
*
|
|
|
|
|
* integer_including_group_separator =
|
|
|
|
|
* regexp([0-9,]*[0-9]+)
|
|
|
|
|
*
|
|
|
|
|
* fraction_including_decimal_separator =
|
|
|
|
|
* regexp((\.[0-9]+)?)
|
|
|
|
|
|
|
|
|
|
* prefix_including_padding = non_number_stuff
|
|
|
|
|
*
|
|
|
|
|
* scientific_notation = regexp(E[+-]?[0-9]+)
|
|
|
|
|
*
|
|
|
|
|
* suffix = non_number_stuff
|
|
|
|
|
*
|
|
|
|
|
* non_number_stuff = regexp([^0-9]*)
|
|
|
|
|
*
|
|
|
|
|
*
|
|
|
|
|
* Regexp groups:
|
|
|
|
|
*
|
|
|
|
|
* 0: number_pattern_re
|
|
|
|
|
* 1: prefix
|
|
|
|
|
* 2: integer_including_group_separator fraction_including_decimal_separator
|
|
|
|
|
* 3: integer_including_group_separator
|
|
|
|
|
* 4: fraction_including_decimal_separator
|
|
|
|
|
* 5: scientific_notation
|
|
|
|
|
* 6: suffix
|
|
|
|
|
*/
|
|
|
|
|
var numberNumberRe = (/^([^0-9]*)(([0-9,]*[0-9]+)(\.[0-9]+)?)(E[+-]?[0-9]+)?([^0-9]*)$/);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* parse( value, properties )
|
|
|
|
|
*
|
|
|
|
|
* @value [String].
|
|
|
|
|
*
|
|
|
|
|
* @properties [Object] Parser properties is a reduced pre-processed cldr
|
|
|
|
|
* data set returned by numberParserProperties().
|
|
|
|
|
*
|
|
|
|
|
* Return the parsed Number (including Infinity) or NaN when value is invalid.
|
|
|
|
|
* ref: http://www.unicode.org/reports/tr35/tr35-numbers.html
|
|
|
|
|
*/
|
|
|
|
|
var numberParse = function( value, properties ) {
|
|
|
|
|
var aux, infinitySymbol, invertedNuDigitsMap, invertedSymbolMap, localizedDigitRe,
|
|
|
|
|
localizedSymbolsRe, negativePrefix, negativeSuffix, number, prefix, suffix;
|
|
|
|
|
|
|
|
|
|
infinitySymbol = properties[ 0 ];
|
|
|
|
|
invertedSymbolMap = properties[ 1 ];
|
|
|
|
|
negativePrefix = properties[ 2 ];
|
|
|
|
|
negativeSuffix = properties[ 3 ];
|
|
|
|
|
invertedNuDigitsMap = properties[ 4 ];
|
|
|
|
|
|
|
|
|
|
// Infinite number.
|
|
|
|
|
if ( aux = value.match( infinitySymbol ) ) {
|
|
|
|
|
|
|
|
|
|
number = Infinity;
|
|
|
|
|
prefix = value.slice( 0, aux.length );
|
|
|
|
|
suffix = value.slice( aux.length + 1 );
|
|
|
|
|
|
|
|
|
|
// Finite number.
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
|
|
// TODO: Create it during setup, i.e., make it a property.
|
|
|
|
|
localizedSymbolsRe = new RegExp(
|
|
|
|
|
Object.keys( invertedSymbolMap ).map(function( localizedSymbol ) {
|
|
|
|
|
return regexpEscape( localizedSymbol );
|
|
|
|
|
}).join( "|" ),
|
|
|
|
|
"g"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Reverse localized symbols.
|
|
|
|
|
value = value.replace( localizedSymbolsRe, function( localizedSymbol ) {
|
|
|
|
|
return invertedSymbolMap[ localizedSymbol ];
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Reverse localized numbering system.
|
|
|
|
|
if ( invertedNuDigitsMap ) {
|
|
|
|
|
|
|
|
|
|
// TODO: Create it during setup, i.e., make it a property.
|
|
|
|
|
localizedDigitRe = new RegExp(
|
|
|
|
|
Object.keys( invertedNuDigitsMap ).map(function( localizedDigit ) {
|
|
|
|
|
return regexpEscape( localizedDigit );
|
|
|
|
|
}).join( "|" ),
|
|
|
|
|
"g"
|
|
|
|
|
);
|
|
|
|
|
value = value.replace( localizedDigitRe, function( localizedDigit ) {
|
|
|
|
|
return invertedNuDigitsMap[ localizedDigit ];
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Is it a valid number?
|
|
|
|
|
value = value.match( numberNumberRe );
|
|
|
|
|
if ( !value ) {
|
|
|
|
|
|
|
|
|
|
// Invalid number.
|
|
|
|
|
return NaN;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
prefix = value[ 1 ];
|
|
|
|
|
suffix = value[ 6 ];
|
|
|
|
|
|
|
|
|
|
// Remove grouping separators.
|
|
|
|
|
number = value[ 2 ].replace( /,/g, "" );
|
|
|
|
|
|
|
|
|
|
// Scientific notation
|
|
|
|
|
if ( value[ 5 ] ) {
|
|
|
|
|
number += value[ 5 ];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
number = +number;
|
|
|
|
|
|
|
|
|
|
// Is it a valid number?
|
|
|
|
|
if ( isNaN( number ) ) {
|
|
|
|
|
|
|
|
|
|
// Invalid number.
|
|
|
|
|
return NaN;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Percent
|
|
|
|
|
if ( value[ 0 ].indexOf( "%" ) !== -1 ) {
|
|
|
|
|
number /= 100;
|
|
|
|
|
suffix = suffix.replace( "%", "" );
|
|
|
|
|
|
|
|
|
|
// Per mille
|
|
|
|
|
} else if ( value[ 0 ].indexOf( "\u2030" ) !== -1 ) {
|
|
|
|
|
number /= 1000;
|
|
|
|
|
suffix = suffix.replace( "\u2030", "" );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Negative number
|
|
|
|
|
// "If there is an explicit negative subpattern, it serves only to specify the negative prefix
|
|
|
|
|
// and suffix. If there is no explicit negative subpattern, the negative subpattern is the
|
|
|
|
|
// localized minus sign prefixed to the positive subpattern" UTS#35
|
|
|
|
|
if ( prefix === negativePrefix && suffix === negativeSuffix ) {
|
|
|
|
|
number *= -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return number;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* symbolMap( cldr )
|
|
|
|
|
*
|
|
|
|
|
* @cldr [Cldr instance].
|
|
|
|
|
*
|
|
|
|
|
* Return the (localized symbol, pattern symbol) key value pair, eg. {
|
|
|
|
|
* "٫": ".",
|
|
|
|
|
* "٬": ",",
|
|
|
|
|
* "٪": "%",
|
|
|
|
|
* ...
|
|
|
|
|
* };
|
|
|
|
|
*/
|
|
|
|
|
var numberSymbolInvertedMap = function( cldr ) {
|
|
|
|
|
var symbol,
|
|
|
|
|
symbolMap = {};
|
|
|
|
|
|
|
|
|
|
for ( symbol in numberSymbolName ) {
|
|
|
|
|
symbolMap[ numberSymbol( numberSymbolName[ symbol ], cldr ) ] = symbol;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return symbolMap;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* parseProperties( pattern, cldr )
|
|
|
|
|
*
|
|
|
|
|
* @pattern [String] raw pattern for numbers.
|
|
|
|
|
*
|
|
|
|
|
* @cldr [Cldr instance].
|
|
|
|
|
*
|
|
|
|
|
* Return parser properties, used to feed parser function.
|
|
|
|
|
*/
|
|
|
|
|
var numberParseProperties = function( pattern, cldr ) {
|
|
|
|
|
var invertedNuDigitsMap, invertedNuDigitsMapSanityCheck, negativePattern, negativeProperties,
|
|
|
|
|
nuDigitsMap = numberNumberingSystemDigitsMap( cldr );
|
|
|
|
|
|
|
|
|
|
pattern = pattern.split( ";" );
|
|
|
|
|
negativePattern = pattern[ 1 ] || "-" + pattern[ 0 ];
|
|
|
|
|
negativeProperties = numberPatternProperties( negativePattern );
|
|
|
|
|
if ( nuDigitsMap ) {
|
|
|
|
|
invertedNuDigitsMap = nuDigitsMap.split( "" ).reduce(function( object, localizedDigit, i ) {
|
|
|
|
|
object[ localizedDigit ] = String( i );
|
|
|
|
|
return object;
|
|
|
|
|
}, {} );
|
|
|
|
|
invertedNuDigitsMapSanityCheck = "0123456789".split( "" ).reduce(function( object, digit ) {
|
|
|
|
|
object[ digit ] = "invalid";
|
|
|
|
|
return object;
|
|
|
|
|
}, {} );
|
|
|
|
|
invertedNuDigitsMap = objectExtend(
|
|
|
|
|
invertedNuDigitsMapSanityCheck,
|
|
|
|
|
invertedNuDigitsMap
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 0: @infinitySymbol [String] Infinity symbol.
|
|
|
|
|
// 1: @invertedSymbolMap [Object] Inverted symbol map augmented with sanity check.
|
|
|
|
|
// The sanity check prevents permissive parsing, i.e., it prevents symbols that doesn't
|
|
|
|
|
// belong to the localized set to pass through. This is obtained with the result of the
|
|
|
|
|
// inverted map object overloading symbol name map object (the remaining symbol name
|
|
|
|
|
// mappings will invalidate parsing, working as the sanity check).
|
|
|
|
|
// 2: @negativePrefix [String] Negative prefix.
|
|
|
|
|
// 3: @negativeSuffix [String] Negative suffix with percent or per mille stripped out.
|
|
|
|
|
// 4: @invertedNuDigitsMap [Object] Inverted digits map if numbering system is different than
|
|
|
|
|
// `latn` augmented with sanity check (similar to invertedSymbolMap).
|
|
|
|
|
return [
|
|
|
|
|
numberSymbol( "infinity", cldr ),
|
|
|
|
|
objectExtend( {}, numberSymbolName, numberSymbolInvertedMap( cldr ) ),
|
|
|
|
|
negativeProperties[ 0 ],
|
|
|
|
|
negativeProperties[ 10 ].replace( "%", "" ).replace( "\u2030", "" ),
|
|
|
|
|
invertedNuDigitsMap
|
|
|
|
|
];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Pattern( style )
|
|
|
|
|
*
|
|
|
|
|
* @style [String] "decimal" (default) or "percent".
|
|
|
|
|
*
|
|
|
|
|
* @cldr [Cldr instance].
|
|
|
|
|
*/
|
|
|
|
|
var numberPattern = function( style, cldr ) {
|
|
|
|
|
if ( style !== "decimal" && style !== "percent" ) {
|
|
|
|
|
throw new Error( "Invalid style" );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return cldr.main([
|
|
|
|
|
"numbers",
|
|
|
|
|
style + "Formats-numberSystem-" + numberNumberingSystem( cldr ),
|
|
|
|
|
"standard"
|
|
|
|
|
]);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* .numberFormatter( [options] )
|
|
|
|
|
*
|
|
|
|
|
* @options [Object]:
|
|
|
|
|
* - style: [String] "decimal" (default) or "percent".
|
|
|
|
|
* - see also number/format options.
|
|
|
|
|
*
|
|
|
|
|
* Return a function that formats a number according to the given options and default/instance
|
|
|
|
|
* locale.
|
|
|
|
|
*/
|
|
|
|
|
Globalize.numberFormatter =
|
|
|
|
|
Globalize.prototype.numberFormatter = function( options ) {
|
|
|
|
|
var cldr, maximumFractionDigits, maximumSignificantDigits, minimumFractionDigits,
|
|
|
|
|
minimumIntegerDigits, minimumSignificantDigits, pattern, properties;
|
|
|
|
|
|
|
|
|
|
validateParameterTypePlainObject( options, "options" );
|
|
|
|
|
|
|
|
|
|
options = options || {};
|
|
|
|
|
cldr = this.cldr;
|
|
|
|
|
|
|
|
|
|
validateDefaultLocale( cldr );
|
|
|
|
|
|
|
|
|
|
cldr.on( "get", validateCldr );
|
|
|
|
|
|
|
|
|
|
if ( options.raw ) {
|
|
|
|
|
pattern = options.raw;
|
|
|
|
|
} else {
|
|
|
|
|
pattern = numberPattern( options.style || "decimal", cldr );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
properties = numberFormatProperties( pattern, cldr, options );
|
|
|
|
|
|
|
|
|
|
cldr.off( "get", validateCldr );
|
|
|
|
|
|
|
|
|
|
minimumIntegerDigits = properties[ 2 ];
|
|
|
|
|
minimumFractionDigits = properties[ 3 ];
|
|
|
|
|
maximumFractionDigits = properties[ 4 ];
|
|
|
|
|
|
|
|
|
|
minimumSignificantDigits = properties[ 5 ];
|
|
|
|
|
maximumSignificantDigits = properties[ 6 ];
|
|
|
|
|
|
|
|
|
|
// Validate significant digit format properties
|
|
|
|
|
if ( !isNaN( minimumSignificantDigits * maximumSignificantDigits ) ) {
|
|
|
|
|
validateParameterRange( minimumSignificantDigits, "minimumSignificantDigits", 1, 21 );
|
|
|
|
|
validateParameterRange( maximumSignificantDigits, "maximumSignificantDigits",
|
|
|
|
|
minimumSignificantDigits, 21 );
|
|
|
|
|
|
|
|
|
|
} else if ( !isNaN( minimumSignificantDigits ) || !isNaN( maximumSignificantDigits ) ) {
|
|
|
|
|
throw new Error( "Neither or both the minimum and maximum significant digits must be " +
|
|
|
|
|
"present" );
|
|
|
|
|
|
|
|
|
|
// Validate integer and fractional format
|
|
|
|
|
} else {
|
|
|
|
|
validateParameterRange( minimumIntegerDigits, "minimumIntegerDigits", 1, 21 );
|
|
|
|
|
validateParameterRange( minimumFractionDigits, "minimumFractionDigits", 0, 20 );
|
|
|
|
|
validateParameterRange( maximumFractionDigits, "maximumFractionDigits",
|
|
|
|
|
minimumFractionDigits, 20 );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return function( value ) {
|
|
|
|
|
validateParameterPresence( value, "value" );
|
|
|
|
|
validateParameterTypeNumber( value, "value" );
|
|
|
|
|
return numberFormat( value, properties );
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* .numberParser( [options] )
|
|
|
|
|
*
|
|
|
|
|
* @options [Object]:
|
|
|
|
|
* - style: [String] "decimal" (default) or "percent".
|
|
|
|
|
*
|
|
|
|
|
* Return the number parser according to the default/instance locale.
|
|
|
|
|
*/
|
|
|
|
|
Globalize.numberParser =
|
|
|
|
|
Globalize.prototype.numberParser = function( options ) {
|
|
|
|
|
var cldr, pattern, properties;
|
|
|
|
|
|
|
|
|
|
validateParameterTypePlainObject( options, "options" );
|
|
|
|
|
|
|
|
|
|
options = options || {};
|
|
|
|
|
cldr = this.cldr;
|
|
|
|
|
|
|
|
|
|
validateDefaultLocale( cldr );
|
|
|
|
|
|
|
|
|
|
cldr.on( "get", validateCldr );
|
|
|
|
|
|
|
|
|
|
if ( options.raw ) {
|
|
|
|
|
pattern = options.raw;
|
|
|
|
|
} else {
|
|
|
|
|
pattern = numberPattern( options.style || "decimal", cldr );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
properties = numberParseProperties( pattern, cldr );
|
|
|
|
|
|
|
|
|
|
cldr.off( "get", validateCldr );
|
|
|
|
|
|
|
|
|
|
return function( value ) {
|
|
|
|
|
validateParameterPresence( value, "value" );
|
|
|
|
|
validateParameterTypeString( value, "value" );
|
|
|
|
|
return numberParse( value, properties );
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* .formatNumber( value [, options] )
|
|
|
|
|
*
|
|
|
|
|
* @value [Number] number to be formatted.
|
|
|
|
|
*
|
|
|
|
|
* @options [Object]: see number/format-properties.
|
|
|
|
|
*
|
|
|
|
|
* Format a number according to the given options and default/instance locale.
|
|
|
|
|
*/
|
|
|
|
|
Globalize.formatNumber =
|
|
|
|
|
Globalize.prototype.formatNumber = function( value, options ) {
|
|
|
|
|
validateParameterPresence( value, "value" );
|
|
|
|
|
validateParameterTypeNumber( value, "value" );
|
|
|
|
|
|
|
|
|
|
return this.numberFormatter( options )( value );
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* .parseNumber( value [, options] )
|
|
|
|
|
*
|
|
|
|
|
* @value [String]
|
|
|
|
|
*
|
|
|
|
|
* @options [Object]: See numberParser().
|
|
|
|
|
*
|
|
|
|
|
* Return the parsed Number (including Infinity) or NaN when value is invalid.
|
|
|
|
|
*/
|
|
|
|
|
Globalize.parseNumber =
|
|
|
|
|
Globalize.prototype.parseNumber = function( value, options ) {
|
|
|
|
|
validateParameterPresence( value, "value" );
|
|
|
|
|
validateParameterTypeString( value, "value" );
|
|
|
|
|
|
|
|
|
|
return this.numberParser( options )( value );
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Optimization to avoid duplicating some internal functions across modules.
|
|
|
|
|
*/
|
|
|
|
|
Globalize._createErrorUnsupportedFeature = createErrorUnsupportedFeature;
|
|
|
|
|
Globalize._numberNumberingSystem = numberNumberingSystem;
|
|
|
|
|
Globalize._numberPattern = numberPattern;
|
|
|
|
|
Globalize._numberSymbol = numberSymbol;
|
|
|
|
|
Globalize._stringPad = stringPad;
|
|
|
|
|
Globalize._validateParameterTypeNumber = validateParameterTypeNumber;
|
|
|
|
|
Globalize._validateParameterTypeString = validateParameterTypeString;
|
|
|
|
|
|
|
|
|
|
return Globalize;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}));
|